dev-api: add developer portal, OpenAPI spec, and Flatenbadet case study
- New /developers/ page with API docs, SDKs, pricing, use cases - OpenAPI 3.0 spec for Orders, Missions, Photos, Analytics - Case study: Glasskiosken i Flatenbadet — complete ROI analysis - Updated /order/ with recurring missions and frequency dropdown
This commit is contained in:
+1446
@@ -0,0 +1,1446 @@
|
||||
# API
|
||||
|
||||
* [pino() => logger](#export)
|
||||
* [options](#options)
|
||||
* [destination](#destination)
|
||||
* [destination\[Symbol.for('pino.metadata')\]](#metadata)
|
||||
* [Logger Instance](#logger)
|
||||
* [logger.trace()](#trace)
|
||||
* [logger.debug()](#debug)
|
||||
* [logger.info()](#info)
|
||||
* [logger.warn()](#warn)
|
||||
* [logger.error()](#error)
|
||||
* [logger.fatal()](#fatal)
|
||||
* [logger.silent()](#silent)
|
||||
* [logger.child()](#child)
|
||||
* [logger.bindings()](#logger-bindings)
|
||||
* [logger.setBindings()](#logger-set-bindings)
|
||||
* [logger.flush()](#flush)
|
||||
* [logger.level](#logger-level)
|
||||
* [logger.isLevelEnabled()](#islevelenabled)
|
||||
* [logger.levels](#levels)
|
||||
* [logger\[Symbol.for('pino.serializers')\]](#serializers)
|
||||
* [Event: 'level-change'](#level-change)
|
||||
* [logger.version](#version)
|
||||
* [Statics](#statics)
|
||||
* [pino.destination()](#pino-destination)
|
||||
* [pino.transport()](#pino-transport)
|
||||
* [pino.multistream()](#pino-multistream)
|
||||
* [pino.stdSerializers](#pino-stdserializers)
|
||||
* [pino.stdTimeFunctions](#pino-stdtimefunctions)
|
||||
* [pino.symbols](#pino-symbols)
|
||||
* [pino.version](#pino-version)
|
||||
* [Interfaces](#interfaces)
|
||||
* [MultiStreamRes](#multistreamres)
|
||||
* [StreamEntry](#streamentry)
|
||||
* [DestinationStream](#destinationstream)
|
||||
* [Types](#types)
|
||||
* [Level](#level-1)
|
||||
|
||||
<a id="export"></a>
|
||||
## `pino([options], [destination]) => logger`
|
||||
|
||||
The exported `pino` function takes two optional arguments,
|
||||
[`options`](#options) and [`destination`](#destination), and
|
||||
returns a [logger instance](#logger).
|
||||
|
||||
<a id=options></a>
|
||||
### `options` (Object)
|
||||
|
||||
#### `name` (String)
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
The name of the logger. When set adds a `name` field to every JSON line logged.
|
||||
|
||||
#### `level` (String)
|
||||
|
||||
Default: `'info'`
|
||||
|
||||
The minimum level to log: Pino will not log messages with a lower level. Setting this option reduces the load, as typically, debug and trace logs are only valid for development, and not needed in production.
|
||||
|
||||
One of `'fatal'`, `'error'`, `'warn'`, `'info'`, `'debug'`, `'trace'` or `'silent'`.
|
||||
|
||||
Additional levels can be added to the instance via the `customLevels` option.
|
||||
|
||||
* See [`customLevels` option](#opt-customlevels)
|
||||
|
||||
<a id=opt-customlevels></a>
|
||||
|
||||
#### `levelComparison` ("ASC", "DESC", Function)
|
||||
|
||||
Default: `ASC`
|
||||
|
||||
Use this option to customize levels order.
|
||||
In order to be able to define custom levels ordering pass a function which will accept `current` and `expected` values and return `boolean` which shows should `current` level to be shown or not.
|
||||
|
||||
```js
|
||||
const logger = pino({
|
||||
levelComparison: 'DESC',
|
||||
customLevels: {
|
||||
foo: 20, // `foo` is more valuable than `bar`
|
||||
bar: 10
|
||||
},
|
||||
})
|
||||
|
||||
// OR
|
||||
|
||||
const logger = pino({
|
||||
levelComparison: function(current, expected) {
|
||||
return current >= expected;
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### `customLevels` (Object)
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
Use this option to define additional logging levels.
|
||||
The keys of the object correspond to the namespace of the log level,
|
||||
and the values should be the numerical value of the level.
|
||||
|
||||
```js
|
||||
const logger = pino({
|
||||
customLevels: {
|
||||
foo: 35
|
||||
}
|
||||
})
|
||||
logger.foo('hi')
|
||||
```
|
||||
|
||||
<a id=opt-useOnlyCustomLevels></a>
|
||||
#### `useOnlyCustomLevels` (Boolean)
|
||||
|
||||
Default: `false`
|
||||
|
||||
Use this option to only use defined `customLevels` and omit Pino's levels.
|
||||
Logger's default `level` must be changed to a value in `customLevels` to use `useOnlyCustomLevels`
|
||||
Warning: this option may not be supported by downstream transports.
|
||||
|
||||
```js
|
||||
const logger = pino({
|
||||
customLevels: {
|
||||
foo: 35
|
||||
},
|
||||
useOnlyCustomLevels: true,
|
||||
level: 'foo'
|
||||
})
|
||||
logger.foo('hi')
|
||||
logger.info('hello') // Will throw an error saying info is not found in logger object
|
||||
```
|
||||
#### `depthLimit` (Number)
|
||||
|
||||
Default: `5`
|
||||
|
||||
Option to limit stringification at a specific nesting depth when logging circular objects.
|
||||
|
||||
#### `edgeLimit` (Number)
|
||||
|
||||
Default: `100`
|
||||
|
||||
Option to limit stringification of properties/elements when logging a specific object/array with circular references.
|
||||
|
||||
<a id="opt-mixin"></a>
|
||||
#### `mixin` (Function):
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
If provided, the `mixin` function is called each time one of the active
|
||||
logging methods is called. The first parameter is the value `mergeObject` or an empty object. The second parameter is the log level number.
|
||||
The third parameter is the logger or child logger itself, which can be used to
|
||||
retrieve logger-specific context from within the `mixin` function.
|
||||
The function must synchronously return an object. The properties of the returned object will be added to the
|
||||
logged JSON.
|
||||
|
||||
```js
|
||||
let n = 0
|
||||
const logger = pino({
|
||||
mixin () {
|
||||
return { line: ++n }
|
||||
}
|
||||
})
|
||||
logger.info('hello')
|
||||
// {"level":30,"time":1573664685466,"pid":78742,"hostname":"x","line":1,"msg":"hello"}
|
||||
logger.info('world')
|
||||
// {"level":30,"time":1573664685469,"pid":78742,"hostname":"x","line":2,"msg":"world"}
|
||||
```
|
||||
|
||||
The result of `mixin()` is supposed to be a _new_ object. For performance reason, the object returned by `mixin()` will be mutated by pino.
|
||||
In the following example, passing `mergingObject` argument to the first `info` call will mutate the global `mixin` object by default:
|
||||
(* See [`mixinMergeStrategy` option](#opt-mixin-merge-strategy)):
|
||||
```js
|
||||
const mixin = {
|
||||
appName: 'My app'
|
||||
}
|
||||
|
||||
const logger = pino({
|
||||
mixin() {
|
||||
return mixin;
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({
|
||||
description: 'Ok'
|
||||
}, 'Message 1')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","appName":"My app","description":"Ok","msg":"Message 1"}
|
||||
logger.info('Message 2')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","appName":"My app","description":"Ok","msg":"Message 2"}
|
||||
// Note: the second log contains "description":"Ok" text, even if it was not provided.
|
||||
```
|
||||
|
||||
The `mixin` method can be used to add the level label to each log message such as in the following example:
|
||||
```js
|
||||
const logger = pino({
|
||||
mixin(_context, level) {
|
||||
return { 'level-label': logger.levels.labels[level] }
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({
|
||||
description: 'Ok'
|
||||
}, 'Message 1')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","description":"Ok","level-label":"info","msg":"Message 1"}
|
||||
logger.error('Message 2')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","level-label":"error","msg":"Message 2"}
|
||||
```
|
||||
|
||||
If the `mixin` feature is being used merely to add static metadata to each log message,
|
||||
then a [child logger ⇗](/docs/child-loggers.md) should be used instead. Unless your application
|
||||
needs to concatenate values for a specific key multiple times, in which case `mixin` can be
|
||||
used to avoid the [duplicate keys caveat](/docs/child-loggers.md#duplicate-keys-caveat):
|
||||
|
||||
```js
|
||||
const logger = pino({
|
||||
mixin (obj, num, logger) {
|
||||
return {
|
||||
tags: logger.tags
|
||||
}
|
||||
}
|
||||
})
|
||||
logger.tags = {}
|
||||
|
||||
logger.addTag = function (key, value) {
|
||||
logger.tags[key] = value
|
||||
}
|
||||
|
||||
function createChild (parent, ...context) {
|
||||
const newChild = logger.child(...context)
|
||||
newChild.tags = { ...logger.tags }
|
||||
newChild.addTag = function (key, value) {
|
||||
newChild.tags[key] = value
|
||||
}
|
||||
return newChild
|
||||
}
|
||||
|
||||
logger.addTag('foo', 1)
|
||||
const child = createChild(logger, {})
|
||||
child.addTag('bar', 2)
|
||||
logger.info('this will only have `foo: 1`')
|
||||
child.info('this will have both `foo: 1` and `bar: 2`')
|
||||
logger.info('this will still only have `foo: 1`')
|
||||
```
|
||||
|
||||
As of pino 7.x, when the `mixin` is used with the [`nestedKey` option](#opt-nestedkey),
|
||||
the object returned from the `mixin` method will also be nested. Prior versions would mix
|
||||
this object into the root.
|
||||
|
||||
```js
|
||||
const logger = pino({
|
||||
nestedKey: 'payload',
|
||||
mixin() {
|
||||
return { requestId: requestId.currentId() }
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({
|
||||
description: 'Ok'
|
||||
}, 'Message 1')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","payload":{"requestId":"dfe9a9014b","description":"Ok"},"msg":"Message 1"}
|
||||
```
|
||||
|
||||
<a id="opt-mixin-merge-strategy"></a>
|
||||
#### `mixinMergeStrategy` (Function):
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
If provided, the `mixinMergeStrategy` function is called each time one of the active
|
||||
logging methods is called. The first parameter is the value `mergeObject` or an empty object,
|
||||
the second parameter is the value resulting from `mixin()` (* See [`mixin` option](#opt-mixin) or an empty object.
|
||||
The function must synchronously return an object.
|
||||
|
||||
```js
|
||||
// Default strategy, `mergeObject` has priority
|
||||
const logger = pino({
|
||||
mixin() {
|
||||
return { tag: 'docker' }
|
||||
},
|
||||
// mixinMergeStrategy(mergeObject, mixinObject) {
|
||||
// return Object.assign(mixinMeta, mergeObject)
|
||||
// }
|
||||
})
|
||||
|
||||
logger.info({
|
||||
tag: 'local'
|
||||
}, 'Message')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","tag":"local","msg":"Message"}
|
||||
```
|
||||
|
||||
```js
|
||||
// Custom mutable strategy, `mixin` has priority
|
||||
const logger = pino({
|
||||
mixin() {
|
||||
return { tag: 'k8s' }
|
||||
},
|
||||
mixinMergeStrategy(mergeObject, mixinObject) {
|
||||
return Object.assign(mergeObject, mixinObject)
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({
|
||||
tag: 'local'
|
||||
}, 'Message')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","tag":"k8s","msg":"Message"}
|
||||
```
|
||||
|
||||
```js
|
||||
// Custom immutable strategy, `mixin` has priority
|
||||
const logger = pino({
|
||||
mixin() {
|
||||
return { tag: 'k8s' }
|
||||
},
|
||||
mixinMergeStrategy(mergeObject, mixinObject) {
|
||||
return Object.assign({}, mergeObject, mixinObject)
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({
|
||||
tag: 'local'
|
||||
}, 'Message')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","tag":"k8s","msg":"Message"}
|
||||
```
|
||||
|
||||
<a id="opt-redact"></a>
|
||||
#### `redact` (Array | Object):
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
As an array, the `redact` option specifies paths that should
|
||||
have their values redacted from any log output.
|
||||
|
||||
Each path must be a string using a syntax that corresponds to JavaScript dot and bracket notation.
|
||||
|
||||
If an object is supplied, three options can be specified:
|
||||
* `paths` (array): Required. An array of paths. See [redaction - Path Syntax ⇗](/docs/redaction.md#paths) for specifics.
|
||||
* `censor` (String|Function|Undefined): Optional. When supplied as a String the `censor` option will overwrite keys that are to be redacted. When set to `undefined` the key will be removed entirely from the object.
|
||||
The `censor` option may also be a mapping function. The (synchronous) mapping function has the signature `(value, path) => redactedValue` and is called with the unredacted `value` and `path` to the key being redacted, as an array. For example given a redaction path of `a.b.c` the `path` argument would be `['a', 'b', 'c']`. The value returned from the mapping function becomes the applied censor value. Default: `'[Redacted]'`
|
||||
value synchronously.
|
||||
Default: `'[Redacted]'`
|
||||
* `remove` (Boolean): Optional. Instead of censoring the value, remove both the key and the value. Default: `false`
|
||||
|
||||
**WARNING**: Never allow user input to define redacted paths.
|
||||
|
||||
* See the [redaction ⇗](/docs/redaction.md) documentation.
|
||||
* See [fast-redact#caveat ⇗](https://github.com/davidmarkclements/fast-redact#caveat)
|
||||
|
||||
<a id=opt-hooks></a>
|
||||
#### `hooks` (Object)
|
||||
|
||||
An object mapping to hook functions. Hook functions allow for customizing
|
||||
internal logger operations. Hook functions ***must*** be synchronous functions.
|
||||
|
||||
<a id="logmethod"></a>
|
||||
##### `logMethod`
|
||||
|
||||
Allows for manipulating the parameters passed to logger methods. The signature
|
||||
for this hook is `logMethod (args, method, level) {}`, where `args` is an array
|
||||
of the arguments that were passed to the log method and `method` is the log
|
||||
method itself, `level` is the log level itself. This hook ***must*** invoke the
|
||||
`method` function by using apply, like so: `method.apply(this, newArgumentsArray)`.
|
||||
|
||||
For example, Pino expects a binding object to be the first parameter with an
|
||||
optional string message as the second parameter. Using this hook the parameters
|
||||
can be flipped:
|
||||
|
||||
```js
|
||||
const hooks = {
|
||||
logMethod (inputArgs, method, level) {
|
||||
if (inputArgs.length >= 2) {
|
||||
const arg1 = inputArgs.shift()
|
||||
const arg2 = inputArgs.shift()
|
||||
return method.apply(this, [arg2, arg1, ...inputArgs])
|
||||
}
|
||||
return method.apply(this, inputArgs)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a id=opt-formatters></a>
|
||||
#### `formatters` (Object)
|
||||
|
||||
An object containing functions for formatting the shape of the log lines.
|
||||
These functions should return a JSONifiable object and
|
||||
should never throw. These functions allow for full customization of
|
||||
the resulting log lines. For example, they can be used to change
|
||||
the level key name or to enrich the default metadata.
|
||||
|
||||
##### `level`
|
||||
|
||||
Changes the shape of the log level. The default shape is `{ level: number }`.
|
||||
The function takes two arguments, the label of the level (e.g. `'info'`)
|
||||
and the numeric value (e.g. `30`).
|
||||
|
||||
ps: The log level cannot be customized when using multiple transports
|
||||
|
||||
```js
|
||||
const formatters = {
|
||||
level (label, number) {
|
||||
return { level: number }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### `bindings`
|
||||
|
||||
Changes the shape of the bindings. The default shape is `{ pid, hostname }`.
|
||||
The function takes a single argument, the bindings object, which can be configured
|
||||
using the [`base` option](#opt-base). Called once when creating logger.
|
||||
|
||||
```js
|
||||
const formatters = {
|
||||
bindings (bindings) {
|
||||
return { pid: bindings.pid, hostname: bindings.hostname }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### `log`
|
||||
|
||||
Changes the shape of the log object. This function will be called every time
|
||||
one of the log methods (such as `.info`) is called. All arguments passed to the
|
||||
log method, except the message, will be passed to this function. By default, it does
|
||||
not change the shape of the log object.
|
||||
|
||||
```js
|
||||
const formatters = {
|
||||
log (object) {
|
||||
return object
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a id=opt-serializers></a>
|
||||
#### `serializers` (Object)
|
||||
|
||||
Default: `{err: pino.stdSerializers.err}`
|
||||
|
||||
An object containing functions for custom serialization of objects.
|
||||
These functions should return an JSONifiable object and they
|
||||
should never throw. When logging an object, each top-level property
|
||||
matching the exact key of a serializer will be serialized using the defined serializer.
|
||||
|
||||
The serializers are applied when a property in the logged object matches a property
|
||||
in the serializers. The only exception is the `err` serializer as it is also applied in case
|
||||
the object is an instance of `Error`, e.g. `logger.info(new Error('kaboom'))`.
|
||||
See `errorKey` option to change `err` namespace.
|
||||
|
||||
* See [pino.stdSerializers](#pino-stdserializers)
|
||||
|
||||
#### `msgPrefix` (String)
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
The `msgPrefix` property allows you to specify a prefix for every message of the logger and its children.
|
||||
|
||||
```js
|
||||
const logger = pino({
|
||||
msgPrefix: '[HTTP] '
|
||||
})
|
||||
logger.info('got new request!')
|
||||
// > [HTTP] got new request!
|
||||
|
||||
const child = logger.child({})
|
||||
child.info('User authenticated!')
|
||||
// > [HTTP] User authenticated!
|
||||
```
|
||||
|
||||
<a id=opt-base></a>
|
||||
#### `base` (Object)
|
||||
|
||||
Default: `{pid: process.pid, hostname: os.hostname}`
|
||||
|
||||
Key-value object added as child logger to each log line.
|
||||
|
||||
Set to `undefined` to avoid adding `pid`, `hostname` properties to each log.
|
||||
|
||||
#### `enabled` (Boolean)
|
||||
|
||||
Default: `true`
|
||||
|
||||
Set to `false` to disable logging.
|
||||
|
||||
#### `crlf` (Boolean)
|
||||
|
||||
Default: `false`
|
||||
|
||||
Set to `true` to logs newline delimited JSON with `\r\n` instead of `\n`.
|
||||
|
||||
<a id=opt-timestamp></a>
|
||||
#### `timestamp` (Boolean | Function)
|
||||
|
||||
Default: `true`
|
||||
|
||||
Enables or disables the inclusion of a timestamp in the
|
||||
log message. If a function is supplied, it must synchronously return a partial JSON string
|
||||
representation of the time, e.g. `,"time":1493426328206` (which is the default).
|
||||
|
||||
If set to `false`, no timestamp will be included in the output.
|
||||
|
||||
See [stdTimeFunctions](#pino-stdtimefunctions) for a set of available functions
|
||||
for passing in as a value for this option.
|
||||
|
||||
Example:
|
||||
```js
|
||||
timestamp: () => `,"time":"${new Date(Date.now()).toISOString()}"`
|
||||
// which is equivalent to:
|
||||
// timestamp: stdTimeFunctions.isoTime
|
||||
```
|
||||
|
||||
**Caution**: attempting to format time in-process will significantly impact logging performance.
|
||||
|
||||
<a id=opt-messagekey></a>
|
||||
#### `messageKey` (String)
|
||||
|
||||
Default: `'msg'`
|
||||
|
||||
The string key for the 'message' in the JSON object.
|
||||
|
||||
<a id=opt-messagekey></a>
|
||||
#### `errorKey` (String)
|
||||
|
||||
Default: `'err'`
|
||||
|
||||
The string key for the 'error' in the JSON object.
|
||||
|
||||
<a id=opt-nestedkey></a>
|
||||
#### `nestedKey` (String)
|
||||
|
||||
Default: `null`
|
||||
|
||||
If there's a chance that objects being logged have properties that conflict with those from pino itself (`level`, `timestamp`, `pid`, etc)
|
||||
and duplicate keys in your log records are undesirable, pino can be configured with a `nestedKey` option that causes any `object`s that are logged
|
||||
to be placed under a key whose name is the value of `nestedKey`.
|
||||
|
||||
This way, when searching something like Kibana for values, one can consistently search under the configured `nestedKey` value instead of the root log record keys.
|
||||
|
||||
For example,
|
||||
```js
|
||||
const logger = require('pino')({
|
||||
nestedKey: 'payload'
|
||||
})
|
||||
|
||||
const thing = { level: 'hi', time: 'never', foo: 'bar'} // has pino-conflicting properties!
|
||||
logger.info(thing)
|
||||
|
||||
// logs the following:
|
||||
// {"level":30,"time":1578357790020,"pid":91736,"hostname":"x","payload":{"level":"hi","time":"never","foo":"bar"}}
|
||||
```
|
||||
In this way, logged objects' properties don't conflict with pino's standard logging properties,
|
||||
and searching for logged objects can start from a consistent path.
|
||||
|
||||
#### `browser` (Object)
|
||||
|
||||
Browser only, may have `asObject` and `write` keys. This option is separately
|
||||
documented in the [Browser API ⇗](/docs/browser.md) documentation.
|
||||
|
||||
* See [Browser API ⇗](/docs/browser.md)
|
||||
|
||||
#### `transport` (Object)
|
||||
|
||||
The `transport` option is a shorthand for the [pino.transport()](#pino-transport) function.
|
||||
It supports the same input options:
|
||||
```js
|
||||
require('pino')({
|
||||
transport: {
|
||||
target: '/absolute/path/to/my-transport.mjs'
|
||||
}
|
||||
})
|
||||
|
||||
// or multiple transports
|
||||
require('pino')({
|
||||
transport: {
|
||||
targets: [
|
||||
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
|
||||
{ target: 'some-file-transport', options: { destination: '/dev/null' }
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
If the transport option is supplied to `pino`, a [`destination`](#destination) parameter may not also be passed as a separate argument to `pino`:
|
||||
|
||||
```js
|
||||
pino({ transport: {}}, '/path/to/somewhere') // THIS WILL NOT WORK, DO NOT DO THIS
|
||||
pino({ transport: {}}, process.stderr) // THIS WILL NOT WORK, DO NOT DO THIS
|
||||
```
|
||||
|
||||
when using the `transport` option. In this case, an `Error` will be thrown.
|
||||
|
||||
* See [pino.transport()](#pino-transport)
|
||||
|
||||
#### `onChild` (Function)
|
||||
|
||||
The `onChild` function is a synchronous callback that will be called on each creation of a new child, passing the child instance as its first argument.
|
||||
Any error thrown inside the callback will be uncaught and should be handled inside the callback.
|
||||
```js
|
||||
const parent = require('pino')({ onChild: (instance) => {
|
||||
// Execute call back code for each newly created child.
|
||||
}})
|
||||
// `onChild` will now be executed with the new child.
|
||||
parent.child(bindings)
|
||||
```
|
||||
|
||||
|
||||
<a id="destination"></a>
|
||||
### `destination` (Number | String | Object | DestinationStream | SonicBoomOpts | WritableStream)
|
||||
|
||||
Default: `pino.destination(1)` (STDOUT)
|
||||
|
||||
The `destination` parameter can be a file descriptor, a file path, or an
|
||||
object with `dest` property pointing to a fd or path.
|
||||
An ordinary Node.js `stream` file descriptor can be passed as the
|
||||
destination (such as the result
|
||||
of `fs.createWriteStream`) but for peak log writing performance, it is strongly
|
||||
recommended to use `pino.destination` to create the destination stream.
|
||||
Note that the `destination` parameter can be the result of `pino.transport()`.
|
||||
|
||||
```js
|
||||
// pino.destination(1) by default
|
||||
const stdoutLogger = require('pino')()
|
||||
|
||||
// destination param may be in first position when no options:
|
||||
const fileLogger = require('pino')( pino.destination('/log/path'))
|
||||
|
||||
// use the stderr file handle to log to stderr:
|
||||
const opts = {name: 'my-logger'}
|
||||
const stderrLogger = require('pino')(opts, pino.destination(2))
|
||||
|
||||
// automatic wrapping in pino.destination
|
||||
const fileLogger = require('pino')('/log/path')
|
||||
|
||||
// Asynchronous logging
|
||||
const fileLogger = pino(pino.destination({ dest: '/log/path', sync: false }))
|
||||
```
|
||||
|
||||
However, there are some special instances where `pino.destination` is not used as the default:
|
||||
|
||||
+ When something, e.g a process manager, has monkey-patched `process.stdout.write`.
|
||||
|
||||
In these cases `process.stdout` is used instead.
|
||||
|
||||
Note: If the parameter is a string integer, e.g. `'1'`, it will be coerced to
|
||||
a number and used as a file descriptor. If this is not desired, provide a full
|
||||
path, e.g. `/tmp/1`.
|
||||
|
||||
* See [`pino.destination`](#pino-destination)
|
||||
|
||||
<a id="metadata"></a>
|
||||
#### `destination[Symbol.for('pino.metadata')]`
|
||||
|
||||
Default: `false`
|
||||
|
||||
Using the global symbol `Symbol.for('pino.metadata')` as a key on the `destination` parameter and
|
||||
setting the key to `true`, indicates that the following properties should be
|
||||
set on the `destination` object after each log line is written:
|
||||
|
||||
* the last logging level as `destination.lastLevel`
|
||||
* the last logging message as `destination.lastMsg`
|
||||
* the last logging object as `destination.lastObj`
|
||||
* the last time as `destination.lastTime`, which will be the partial string returned
|
||||
by the time function.
|
||||
* the last logger instance as `destination.lastLogger` (to support child
|
||||
loggers)
|
||||
|
||||
The following is a succinct usage example:
|
||||
|
||||
```js
|
||||
const dest = pino.destination('/dev/null')
|
||||
dest[Symbol.for('pino.metadata')] = true
|
||||
const logger = pino(dest)
|
||||
logger.info({a: 1}, 'hi')
|
||||
const { lastMsg, lastLevel, lastObj, lastTime} = dest
|
||||
console.log(
|
||||
'Logged message "%s" at level %d with object %o at time %s',
|
||||
lastMsg, lastLevel, lastObj, lastTime
|
||||
) // Logged message "hi" at level 30 with object { a: 1 } at time 1531590545089
|
||||
```
|
||||
|
||||
<a id="logger"></a>
|
||||
## Logger Instance
|
||||
|
||||
The logger instance is the object returned by the main exported
|
||||
[`pino`](#export) function.
|
||||
|
||||
The primary purpose of the logger instance is to provide logging methods.
|
||||
|
||||
The default logging methods are `trace`, `debug`, `info`, `warn`, `error`, and `fatal`.
|
||||
|
||||
Each logging method has the following signature:
|
||||
`([mergingObject], [message], [...interpolationValues])`.
|
||||
|
||||
The parameters are explained below using the `logger.info` method but the same applies to all logging methods.
|
||||
|
||||
### Logging Method Parameters
|
||||
|
||||
<a id=mergingobject></a>
|
||||
#### `mergingObject` (Object)
|
||||
|
||||
An object can optionally be supplied as the first parameter. Each enumerable key and value
|
||||
of the `mergingObject` is copied into the JSON log line.
|
||||
|
||||
```js
|
||||
logger.info({MIX: {IN: true}})
|
||||
// {"level":30,"time":1531254555820,"pid":55956,"hostname":"x","MIX":{"IN":true}}
|
||||
```
|
||||
|
||||
If the object is of type Error, it is wrapped in an object containing a property err (`{ err: mergingObject }`).
|
||||
This allows for a unified error handling flow.
|
||||
|
||||
Options `serializers` and `errorKey` could be used at instantiation time to change the namespace
|
||||
from `err` to another string as preferred.
|
||||
|
||||
<a id="message"></a>
|
||||
#### `message` (String)
|
||||
|
||||
A `message` string can optionally be supplied as the first parameter, or
|
||||
as the second parameter after supplying a `mergingObject`.
|
||||
|
||||
By default, the contents of the `message` parameter will be merged into the
|
||||
JSON log line under the `msg` key:
|
||||
|
||||
```js
|
||||
logger.info('hello world')
|
||||
// {"level":30,"time":1531257112193,"msg":"hello world","pid":55956,"hostname":"x"}
|
||||
```
|
||||
|
||||
The `message` parameter takes precedence over the `mergingObject`.
|
||||
That is, if a `mergingObject` contains a `msg` property, and a `message` parameter
|
||||
is supplied in addition, the `msg` property in the output log will be the value of
|
||||
the `message` parameter not the value of the `msg` property on the `mergingObject`.
|
||||
See [Avoid Message Conflict](/docs/help.md#avoid-message-conflict) for information
|
||||
on how to overcome this limitation.
|
||||
|
||||
If no `message` parameter is provided, and the `mergingObject` is of type `Error` or it has a property named `err`, the
|
||||
`message` parameter is set to the `message` value of the error. See option `errorKey` if you want to change the namespace.
|
||||
|
||||
The `messageKey` option can be used at instantiation time to change the namespace
|
||||
from `msg` to another string as preferred.
|
||||
|
||||
The `message` string may contain a printf style string with support for
|
||||
the following placeholders:
|
||||
|
||||
* `%s` – string placeholder
|
||||
* `%d` – digit placeholder
|
||||
* `%O`, `%o`, and `%j` – object placeholder
|
||||
|
||||
Values supplied as additional arguments to the logger method will
|
||||
then be interpolated accordingly.
|
||||
|
||||
* See [`messageKey` pino option](#opt-messagekey)
|
||||
* See [`...interpolationValues` log method parameter](#interpolationvalues)
|
||||
|
||||
<a id="interpolationvalues"></a>
|
||||
#### `...interpolationValues` (Any)
|
||||
|
||||
All arguments supplied after `message` are serialized and interpolated according
|
||||
to any supplied printf-style placeholders (`%s`, `%d`, `%o`|`%O`|`%j`) to form
|
||||
the final output `msg` value for the JSON log line.
|
||||
|
||||
```js
|
||||
logger.info('%o hello %s', {worldly: 1}, 'world')
|
||||
// {"level":30,"time":1531257826880,"msg":"{\"worldly\":1} hello world","pid":55956,"hostname":"x"}
|
||||
```
|
||||
|
||||
Since pino v6, we do not automatically concatenate and cast to string
|
||||
consecutive parameters:
|
||||
|
||||
```js
|
||||
logger.info('hello', 'world')
|
||||
// {"level":30,"time":1531257618044,"msg":"hello","pid":55956,"hostname":"x"}
|
||||
// world is missing
|
||||
```
|
||||
|
||||
However, it's possible to inject a hook to modify this behavior:
|
||||
|
||||
```js
|
||||
const pinoOptions = {
|
||||
hooks: { logMethod }
|
||||
}
|
||||
|
||||
function logMethod (args, method) {
|
||||
if (args.length === 2) {
|
||||
args[0] = `${args[0]} %j`
|
||||
}
|
||||
method.apply(this, args)
|
||||
}
|
||||
|
||||
const logger = pino(pinoOptions)
|
||||
```
|
||||
|
||||
* See [`message` log method parameter](#message)
|
||||
* See [`logMethod` hook](#logmethod)
|
||||
|
||||
<a id="error-serialization"></a>
|
||||
#### Errors
|
||||
|
||||
Errors can be supplied as either the first parameter or if already using `mergingObject` then as the `err` property on the `mergingObject`.
|
||||
|
||||
Options `serializers` and `errorKey` could be used at instantiation time to change the namespace
|
||||
from `err` to another string as preferred.
|
||||
|
||||
> ## Note
|
||||
> This section describes the default configuration. The error serializer can be
|
||||
> mapped to a different key using the [`serializers`](#opt-serializers) option.
|
||||
```js
|
||||
logger.info(new Error("test"))
|
||||
// {"level":30,"time":1531257618044,"msg":"test","stack":"...","type":"Error","pid":55956,"hostname":"x"}
|
||||
|
||||
logger.info({ err: new Error("test"), otherkey: 123 }, "some text")
|
||||
// {"level":30,"time":1531257618044,"err":{"msg": "test", "stack":"...","type":"Error"},"msg":"some text","pid":55956,"hostname":"x","otherkey":123}
|
||||
```
|
||||
|
||||
<a id="trace"></a>
|
||||
### `logger.trace([mergingObject], [message], [...interpolationValues])`
|
||||
|
||||
Write a `'trace'` level log, if the configured [`level`](#level) allows for it.
|
||||
|
||||
* See [`mergingObject` log method parameter](#mergingobject)
|
||||
* See [`message` log method parameter](#message)
|
||||
* See [`...interpolationValues` log method parameter](#interpolationvalues)
|
||||
|
||||
<a id="debug"></a>
|
||||
### `logger.debug([mergingObject], [message], [...interpolationValues])`
|
||||
|
||||
Write a `'debug'` level log, if the configured `level` allows for it.
|
||||
|
||||
* See [`mergingObject` log method parameter](#mergingobject)
|
||||
* See [`message` log method parameter](#message)
|
||||
* See [`...interpolationValues` log method parameter](#interpolationvalues)
|
||||
|
||||
<a id="info"></a>
|
||||
### `logger.info([mergingObject], [message], [...interpolationValues])`
|
||||
|
||||
Write an `'info'` level log, if the configured `level` allows for it.
|
||||
|
||||
* See [`mergingObject` log method parameter](#mergingobject)
|
||||
* See [`message` log method parameter](#message)
|
||||
* See [`...interpolationValues` log method parameter](#interpolationvalues)
|
||||
|
||||
<a id="warn"></a>
|
||||
### `logger.warn([mergingObject], [message], [...interpolationValues])`
|
||||
|
||||
Write a `'warn'` level log, if the configured `level` allows for it.
|
||||
|
||||
* See [`mergingObject` log method parameter](#mergingobject)
|
||||
* See [`message` log method parameter](#message)
|
||||
* See [`...interpolationValues` log method parameter](#interpolationvalues)
|
||||
|
||||
<a id="error"></a>
|
||||
### `logger.error([mergingObject], [message], [...interpolationValues])`
|
||||
|
||||
Write a `'error'` level log, if the configured `level` allows for it.
|
||||
|
||||
* See [`mergingObject` log method parameter](#mergingobject)
|
||||
* See [`message` log method parameter](#message)
|
||||
* See [`...interpolationValues` log method parameter](#interpolationvalues)
|
||||
|
||||
<a id="fatal"></a>
|
||||
### `logger.fatal([mergingObject], [message], [...interpolationValues])`
|
||||
|
||||
Write a `'fatal'` level log, if the configured `level` allows for it.
|
||||
|
||||
Since `'fatal'` level messages are intended to be logged just before the process exiting the `fatal`
|
||||
method will always sync flush the destination.
|
||||
Therefore it's important not to misuse `fatal` since
|
||||
it will cause performance overhead if used for any
|
||||
other purpose than writing final log messages before
|
||||
the process crashes or exits.
|
||||
|
||||
* See [`mergingObject` log method parameter](#mergingobject)
|
||||
* See [`message` log method parameter](#message)
|
||||
* See [`...interpolationValues` log method parameter](#interpolationvalues)
|
||||
|
||||
<a id="silent"><a>
|
||||
### `logger.silent()`
|
||||
|
||||
Noop function.
|
||||
|
||||
<a id="child"></a>
|
||||
### `logger.child(bindings, [options]) => logger`
|
||||
|
||||
The `logger.child` method allows for the creation of stateful loggers,
|
||||
where key-value pairs can be pinned to a logger causing them to be output
|
||||
on every log line.
|
||||
|
||||
Child loggers use the same output stream as the parent and inherit
|
||||
the current log level of the parent at the time they are spawned.
|
||||
|
||||
The log level of a child is mutable. It can be set independently
|
||||
of the parent either by setting the [`level`](#level) accessor after creating
|
||||
the child logger or using the [`options.level`](#optionslevel-string) key.
|
||||
|
||||
<a id="logger-child-bindings"></a>
|
||||
#### `bindings` (Object)
|
||||
|
||||
An object of key-value pairs to include in every log line output
|
||||
via the returned child logger.
|
||||
|
||||
```js
|
||||
const child = logger.child({ MIX: {IN: 'always'} })
|
||||
child.info('hello')
|
||||
// {"level":30,"time":1531258616689,"msg":"hello","pid":64849,"hostname":"x","MIX":{"IN":"always"}}
|
||||
child.info('child!')
|
||||
// {"level":30,"time":1531258617401,"msg":"child!","pid":64849,"hostname":"x","MIX":{"IN":"always"}}
|
||||
```
|
||||
|
||||
The `bindings` object may contain any key except for reserved configuration keys `level` and `serializers`.
|
||||
|
||||
##### `bindings.serializers` (Object) - DEPRECATED
|
||||
|
||||
Use `options.serializers` instead.
|
||||
|
||||
#### `options` (Object)
|
||||
|
||||
Options for child logger. These options will override the parent logger options.
|
||||
|
||||
##### `options.level` (String)
|
||||
|
||||
The `level` property overrides the log level of the child logger.
|
||||
By default, the parent log level is inherited.
|
||||
After the creation of the child logger, it is also accessible using the [`logger.level`](#logger-level) key.
|
||||
|
||||
```js
|
||||
const logger = pino()
|
||||
logger.debug('nope') // will not log, since default level is info
|
||||
const child = logger.child({foo: 'bar'}, {level: 'debug'})
|
||||
child.debug('debug!') // will log as the `level` property set the level to debug
|
||||
```
|
||||
|
||||
##### `options.msgPrefix` (String)
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
The `msgPrefix` property allows you to specify a prefix for every message of the child logger.
|
||||
By default, the parent prefix is inherited.
|
||||
If the parent already has a prefix, the prefix of the parent and then the child will be displayed.
|
||||
|
||||
```js
|
||||
const logger = pino({
|
||||
msgPrefix: '[HTTP] '
|
||||
})
|
||||
logger.info('got new request!')
|
||||
// > [HTTP] got new request!
|
||||
|
||||
const child = logger.child({avengers: 'assemble'}, {msgPrefix: '[Proxy] '})
|
||||
child.info('message proxied!')
|
||||
// > [HTTP] [Proxy] message proxied!
|
||||
```
|
||||
|
||||
##### `options.redact` (Array | Object)
|
||||
|
||||
Setting `options.redact` to an array or object will override the parent `redact` options. To remove `redact` options inherited from the parent logger set this value as an empty array (`[]`).
|
||||
|
||||
```js
|
||||
const logger = require('pino')({ redact: ['hello'] })
|
||||
logger.info({ hello: 'world' })
|
||||
// {"level":30,"time":1625794363403,"pid":67930,"hostname":"x","hello":"[Redacted]"}
|
||||
const child = logger.child({ foo: 'bar' }, { redact: ['foo'] })
|
||||
logger.info({ hello: 'world' })
|
||||
// {"level":30,"time":1625794553558,"pid":67930,"hostname":"x","hello":"world", "foo": "[Redacted]" }
|
||||
```
|
||||
|
||||
* See [`redact` option](#opt-redact)
|
||||
|
||||
##### `options.serializers` (Object)
|
||||
|
||||
Child loggers inherit the [serializers](#opt-serializers) from the parent logger.
|
||||
|
||||
Setting the `serializers` key of the `options` object will override
|
||||
any configured parent serializers.
|
||||
|
||||
```js
|
||||
const logger = require('pino')()
|
||||
logger.info({test: 'will appear'})
|
||||
// {"level":30,"time":1531259759482,"pid":67930,"hostname":"x","test":"will appear"}
|
||||
const child = logger.child({}, {serializers: {test: () => `child-only serializer`}})
|
||||
child.info({test: 'will be overwritten'})
|
||||
// {"level":30,"time":1531259784008,"pid":67930,"hostname":"x","test":"child-only serializer"}
|
||||
```
|
||||
|
||||
* See [`serializers` option](#opt-serializers)
|
||||
* See [pino.stdSerializers](#pino-stdSerializers)
|
||||
|
||||
<a id="logger-bindings"></a>
|
||||
### `logger.bindings()`
|
||||
|
||||
Returns an object containing all the current bindings, cloned from the ones passed in via `logger.child()`.
|
||||
```js
|
||||
const child = logger.child({ foo: 'bar' })
|
||||
console.log(child.bindings())
|
||||
// { foo: 'bar' }
|
||||
const anotherChild = child.child({ MIX: { IN: 'always' } })
|
||||
console.log(anotherChild.bindings())
|
||||
// { foo: 'bar', MIX: { IN: 'always' } }
|
||||
```
|
||||
|
||||
<a id="logger-set-bindings"></a>
|
||||
### `logger.setBindings(bindings)`
|
||||
|
||||
Adds to the bindings of this logger instance.
|
||||
|
||||
**Note:** Does not overwrite bindings. Can potentially result in duplicate keys in
|
||||
log lines.
|
||||
|
||||
* See [`bindings` parameter in `logger.child`](#logger-child-bindings)
|
||||
|
||||
<a id="flush"></a>
|
||||
### `logger.flush([cb])`
|
||||
|
||||
Flushes the content of the buffer when using `pino.destination({
|
||||
sync: false })`.
|
||||
|
||||
This is an asynchronous, best used as fire and forget, operation.
|
||||
|
||||
The use case is primarily for asynchronous logging, which may buffer
|
||||
log lines while others are being written. The `logger.flush` method can be
|
||||
used to flush the logs
|
||||
on a long interval, say ten seconds. Such a strategy can provide an
|
||||
optimum balance between extremely efficient logging at high demand periods
|
||||
and safer logging at low demand periods.
|
||||
|
||||
If there is a need to wait for the logs to be flushed, a callback should be used.
|
||||
|
||||
* See [`destination` parameter](#destination)
|
||||
* See [Asynchronous Logging ⇗](/docs/asynchronous.md)
|
||||
|
||||
<a id="logger-level"></a>
|
||||
### `logger.level` (String) [Getter/Setter]
|
||||
|
||||
Set this property to the desired logging level.
|
||||
|
||||
The core levels and their values are as follows:
|
||||
|
||||
| | | | | | | | |
|
||||
|:-----------|-------|-------|------|------|-------|-------|---------:|
|
||||
| **Level:** | trace | debug | info | warn | error | fatal | silent |
|
||||
| **Value:** | 10 | 20 | 30 | 40 | 50 | 60 | Infinity |
|
||||
|
||||
The logging level is a *minimum* level based on the associated value of that level.
|
||||
|
||||
For instance if `logger.level` is `info` *(30)* then `info` *(30)*, `warn` *(40)*, `error` *(50)*, and `fatal` *(60)* log methods will be enabled but the `trace` *(10)* and `debug` *(20)* methods, being less than 30, will not.
|
||||
|
||||
The `silent` logging level is a specialized level that will disable all logging,
|
||||
the `silent` log method is a noop function.
|
||||
|
||||
<a id="islevelenabled"></a>
|
||||
### `logger.isLevelEnabled(level)`
|
||||
|
||||
A utility method for determining if a given log level will write to the destination.
|
||||
|
||||
#### `level` (String)
|
||||
|
||||
The given level to check against:
|
||||
|
||||
```js
|
||||
if (logger.isLevelEnabled('debug')) logger.debug('conditional log')
|
||||
```
|
||||
|
||||
#### `levelLabel` (String)
|
||||
|
||||
Defines the method name of the new level.
|
||||
|
||||
* See [`logger.level`](#level)
|
||||
|
||||
#### `levelValue` (Number)
|
||||
|
||||
Defines the associated minimum threshold value for the level, and
|
||||
therefore where it sits in order of priority among other levels.
|
||||
|
||||
* See [`logger.level`](#level)
|
||||
|
||||
<a id="levelVal"></a>
|
||||
### `logger.levelVal` (Number)
|
||||
|
||||
Supplies the integer value for the current logging level.
|
||||
|
||||
```js
|
||||
if (logger.levelVal === 30) {
|
||||
console.log('logger level is `info`')
|
||||
}
|
||||
```
|
||||
|
||||
<a id="levels"></a>
|
||||
### `logger.levels` (Object)
|
||||
|
||||
Levels are mapped to values to determine the minimum threshold that a
|
||||
logging method should be enabled at (see [`logger.level`](#level)).
|
||||
|
||||
The `logger.levels` property holds the mappings between levels and values,
|
||||
and vice versa.
|
||||
|
||||
```sh
|
||||
$ node -p "require('pino')().levels"
|
||||
```
|
||||
|
||||
```js
|
||||
{ labels:
|
||||
{ '10': 'trace',
|
||||
'20': 'debug',
|
||||
'30': 'info',
|
||||
'40': 'warn',
|
||||
'50': 'error',
|
||||
'60': 'fatal' },
|
||||
values:
|
||||
{ fatal: 60, error: 50, warn: 40, info: 30, debug: 20, trace: 10 } }
|
||||
```
|
||||
|
||||
* See [`logger.level`](#level)
|
||||
|
||||
<a id="serializers"></a>
|
||||
### logger\[Symbol.for('pino.serializers')\]
|
||||
|
||||
Returns the serializers as applied to the current logger instance. If a child logger did not
|
||||
register its own serializer upon instantiation the serializers of the parent will be returned.
|
||||
|
||||
<a id="level-change"></a>
|
||||
### Event: 'level-change'
|
||||
|
||||
The logger instance is also an [`EventEmitter ⇗`](https://nodejs.org/dist/latest/docs/api/events.html#events_class_eventemitter)
|
||||
|
||||
A listener function can be attached to a logger via the `level-change` event
|
||||
|
||||
The listener is passed five arguments:
|
||||
|
||||
* `levelLabel` – the new level string, e.g `trace`
|
||||
* `levelValue` – the new level number, e.g `10`
|
||||
* `previousLevelLabel` – the prior level string, e.g `info`
|
||||
* `previousLevelValue` – the prior level number, e.g `30`
|
||||
* `logger` – the logger instance from which the event originated
|
||||
|
||||
```js
|
||||
const logger = require('pino')()
|
||||
logger.on('level-change', (lvl, val, prevLvl, prevVal) => {
|
||||
console.log('%s (%d) was changed to %s (%d)', prevLvl, prevVal, lvl, val)
|
||||
})
|
||||
logger.level = 'trace' // trigger event
|
||||
```
|
||||
|
||||
Please note that due to a [known bug](https://github.com/pinojs/pino/issues/1006), every `logger.child()` call will
|
||||
fire a `level-change` event. These events can be ignored by writing an event handler like:
|
||||
|
||||
```js
|
||||
const logger = require('pino')()
|
||||
logger.on('level-change', function (lvl, val, prevLvl, prevVal, instance) {
|
||||
if (logger !== instance) {
|
||||
return
|
||||
}
|
||||
console.log('%s (%d) was changed to %s (%d)', prevLvl, prevVal, lvl, val)
|
||||
})
|
||||
logger.child({}); // trigger an event by creating a child instance, notice no console.log
|
||||
logger.level = 'trace' // trigger event using actual value change, notice console.log
|
||||
```
|
||||
|
||||
<a id="version"></a>
|
||||
### `logger.version` (String)
|
||||
|
||||
Exposes the Pino package version. Also available on the exported `pino` function.
|
||||
|
||||
* See [`pino.version`](#pino-version)
|
||||
|
||||
## Statics
|
||||
|
||||
<a id="pino-destination"></a>
|
||||
### `pino.destination([opts]) => SonicBoom`
|
||||
|
||||
Create a Pino Destination instance: a stream-like object with
|
||||
significantly more throughput than a standard Node.js stream.
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const logger = pino(pino.destination('./my-file'))
|
||||
const logger2 = pino(pino.destination())
|
||||
const logger3 = pino(pino.destination({
|
||||
dest: './my-file',
|
||||
minLength: 4096, // Buffer before writing
|
||||
sync: false // Asynchronous logging, the default
|
||||
}))
|
||||
const logger4 = pino(pino.destination({
|
||||
dest: './my-file2',
|
||||
sync: true // Synchronous logging
|
||||
}))
|
||||
```
|
||||
|
||||
The `pino.destination` method may be passed a file path or a numerical file descriptor.
|
||||
By default, `pino.destination` will use `process.stdout.fd` (1) as the file descriptor.
|
||||
|
||||
`pino.destination` is implemented on [`sonic-boom` ⇗](https://github.com/mcollina/sonic-boom).
|
||||
|
||||
A `pino.destination` instance can also be used to reopen closed files
|
||||
(for example, for some log rotation scenarios), see [Reopening log files](/docs/help.md#reopening).
|
||||
|
||||
* See [`destination` parameter](#destination)
|
||||
* See [`sonic-boom` ⇗](https://github.com/mcollina/sonic-boom)
|
||||
* See [Reopening log files](/docs/help.md#reopening)
|
||||
* See [Asynchronous Logging ⇗](/docs/asynchronous.md)
|
||||
|
||||
<a id="pino-transport"></a>
|
||||
### `pino.transport(options) => ThreadStream`
|
||||
|
||||
Create 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 transport = pino.transport({
|
||||
targets: [{
|
||||
level: 'info',
|
||||
target: 'pino-pretty' // must be installed separately
|
||||
}, {
|
||||
level: 'trace',
|
||||
target: 'pino/file',
|
||||
options: { destination: '/path/to/store/logs' }
|
||||
}]
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
A pipeline could also be created to transform log lines _before_ sending them:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
pipeline: [{
|
||||
target: 'pino-syslog' // must be installed separately
|
||||
}, {
|
||||
target: 'pino-socket' // must be installed separately
|
||||
}]
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
If `WeakRef`, `WeakMap`, and `FinalizationRegistry` are available in the current runtime (v14.5.0+), then the thread
|
||||
will be automatically terminated in case the stream or logger goes out of scope.
|
||||
The `transport()` function adds a listener to `process.on('beforeExit')` and `process.on('exit')` to ensure the worker
|
||||
is flushed and all data synced before the process exits.
|
||||
|
||||
Note that calling `process.exit()` on the main thread will stop the event loop on the main thread from turning. As a result,
|
||||
using `console.log` and `process.stdout` after the main thread called `process.exit()` will not produce any output.
|
||||
|
||||
If you are embedding/integrating pino within your framework, you will need to make pino aware of the script that is calling it,
|
||||
like so:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const getCaller = require('get-caller-file')
|
||||
|
||||
module.exports = function build () {
|
||||
const logger = pino({
|
||||
transport: {
|
||||
caller: getCaller(),
|
||||
target: 'transport',
|
||||
options: { destination: './destination' }
|
||||
}
|
||||
})
|
||||
return logger
|
||||
}
|
||||
```
|
||||
|
||||
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 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.
|
||||
* `pipeline`: May be specified instead of `target`. Must be an array of transport configurations. Transport configurations include the aforementioned `options` and `target` options. All intermediate steps in the pipeline _must_ be `Transform` streams and not `Writable`.
|
||||
* `dedupe`: See [pino.multistream options](#pino-multistream)
|
||||
|
||||
<a id="pino-multistream"></a>
|
||||
|
||||
### `pino.multistream(streamsArray, opts) => MultiStreamRes`
|
||||
|
||||
Create a stream composed by multiple destination streams and returns an
|
||||
object implementing the [MultiStreamRes](#multistreamres) interface.
|
||||
|
||||
```js
|
||||
var fs = require('fs')
|
||||
var pino = require('pino')
|
||||
var pretty = require('pino-pretty')
|
||||
var streams = [
|
||||
{stream: fs.createWriteStream('/tmp/info.stream.out')},
|
||||
{stream: pretty() },
|
||||
{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. Default is `info`.
|
||||
|
||||
#### 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.multistream` to redirect `error` logs to `process.stderr` and others to `process.stdout`:
|
||||
|
||||
```js
|
||||
var pino = require('pino')
|
||||
var multistream = pino.multistream
|
||||
var streams = [
|
||||
{level: 'debug', 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')
|
||||
```
|
||||
|
||||
<a id="pino-stdserializers"></a>
|
||||
### `pino.stdSerializers` (Object)
|
||||
|
||||
The `pino.stdSerializers` object provides functions for serializing objects common to many projects. The standard serializers are directly imported from [pino-std-serializers](https://github.com/pinojs/pino-std-serializers).
|
||||
|
||||
* See [pino-std-serializers ⇗](https://github.com/pinojs/pino-std-serializers)
|
||||
|
||||
<a id="pino-stdtimefunctions"></a>
|
||||
### `pino.stdTimeFunctions` (Object)
|
||||
|
||||
The [`timestamp`](#opt-timestamp) option can accept a function that determines the
|
||||
`timestamp` value in a log line.
|
||||
|
||||
The `pino.stdTimeFunctions` object provides a very small set of common functions for generating the
|
||||
`timestamp` property. These consist of the following
|
||||
|
||||
* `pino.stdTimeFunctions.epochTime`: Milliseconds since Unix epoch (Default)
|
||||
* `pino.stdTimeFunctions.unixTime`: Seconds since Unix epoch
|
||||
* `pino.stdTimeFunctions.nullTime`: Clears timestamp property (Used when `timestamp: false`)
|
||||
* `pino.stdTimeFunctions.isoTime`: ISO 8601-formatted time in UTC
|
||||
|
||||
* See [`timestamp` option](#opt-timestamp)
|
||||
|
||||
<a id="pino-symbols"></a>
|
||||
### `pino.symbols` (Object)
|
||||
|
||||
For integration purposes with ecosystem and third-party libraries `pino.symbols`
|
||||
exposes the symbols used to hold non-public state and methods on the logger instance.
|
||||
|
||||
Access to the symbols allows logger state to be adjusted, and methods to be overridden or
|
||||
proxied for performant integration where necessary.
|
||||
|
||||
The `pino.symbols` object is intended for library implementers and shouldn't be utilized
|
||||
for general use.
|
||||
|
||||
<a id="pino-version"></a>
|
||||
### `pino.version` (String)
|
||||
|
||||
Exposes the Pino package version. Also available on the logger instance.
|
||||
|
||||
* See [`logger.version`](#version)
|
||||
|
||||
## Interfaces
|
||||
<a id="pino-multistreamres"></a>
|
||||
|
||||
### `MultiStreamRes`
|
||||
Properties:
|
||||
|
||||
* `write(data)`
|
||||
- `data` Object | string
|
||||
- Returns: void
|
||||
|
||||
Write `data` onto the streams held by the current instance.
|
||||
* `add(dest)`
|
||||
- `dest` [StreamEntry](#streamentry) | [DestinationStream](#destinationstream)
|
||||
- Returns: [MultiStreamRes](#multistreamres)
|
||||
|
||||
Add `dest` stream to the array of streams of the current instance.
|
||||
* `flushSync()`
|
||||
- Returns: `undefined`
|
||||
|
||||
Call `flushSync` on each stream held by the current instance.
|
||||
* `minLevel`
|
||||
- number
|
||||
|
||||
The minimum level amongst all the streams held by the current instance.
|
||||
* `streams`
|
||||
- Returns: [StreamEntry[]](#streamentry)
|
||||
|
||||
The array of streams currently held by the current instance.
|
||||
* `clone(level)`
|
||||
- `level` [Level](#level-1)
|
||||
- Returns: [MultiStreamRes](#multistreamres)
|
||||
|
||||
Returns a cloned object of the current instance but with the provided `level`.
|
||||
|
||||
### `StreamEntry`
|
||||
Properties:
|
||||
|
||||
* `stream`
|
||||
- DestinationStream
|
||||
* `level`
|
||||
- Optional: [Level](#level-1)
|
||||
|
||||
### `DestinationStream`
|
||||
Properties:
|
||||
|
||||
* `write(msg)`
|
||||
- `msg` string
|
||||
|
||||
## Types
|
||||
### `Level`
|
||||
|
||||
* Values: `"fatal"` | `"error"` | `"warn"` | `"info"` | `"debug"` | `"trace"`
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# Asynchronous Logging
|
||||
|
||||
Asynchronous logging enables the minimum overhead of Pino.
|
||||
Asynchronous logging works by buffering log messages and writing them in larger chunks.
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const logger = pino(pino.destination({
|
||||
dest: './my-file', // omit for stdout
|
||||
minLength: 4096, // Buffer before writing
|
||||
sync: false // Asynchronous logging
|
||||
}))
|
||||
```
|
||||
|
||||
It's always possible to turn on synchronous logging by passing `sync: true`.
|
||||
In this mode of operation, log messages are directly written to the
|
||||
output stream as the messages are generated with a _blocking_ operation.
|
||||
|
||||
* See [`pino.destination`](/docs/api.md#pino-destination)
|
||||
* `pino.destination` is implemented on [`sonic-boom` ⇗](https://github.com/mcollina/sonic-boom).
|
||||
|
||||
### AWS Lambda
|
||||
|
||||
Asynchronous logging is disabled by default on AWS Lambda or any other environment
|
||||
that modifies `process.stdout`. If forcefully turned on, we recommend calling `dest.flushSync()` at the end
|
||||
of each function execution to avoid losing data.
|
||||
|
||||
## Caveats
|
||||
|
||||
Asynchronous logging has a couple of important caveats:
|
||||
|
||||
* As opposed to the synchronous mode, there is not a one-to-one relationship between
|
||||
calls to logging methods (e.g. `logger.info`) and writes to a log file
|
||||
* There is a possibility of the most recently buffered log messages being lost
|
||||
in case of a system failure, e.g. a power cut.
|
||||
|
||||
See also:
|
||||
|
||||
* [`pino.destination` API](/docs/api.md#pino-destination)
|
||||
* [`destination` parameter](/docs/api.md#destination)
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
|
||||
# Benchmarks
|
||||
|
||||
`pino.info('hello world')`:
|
||||
|
||||
```
|
||||
|
||||
BASIC benchmark averages
|
||||
Bunyan average: 377.434ms
|
||||
Winston average: 270.249ms
|
||||
Bole average: 172.690ms
|
||||
Debug average: 220.527ms
|
||||
LogLevel average: 222.802ms
|
||||
Pino average: 114.801ms
|
||||
PinoMinLength average: 70.968ms
|
||||
PinoNodeStream average: 159.192ms
|
||||
|
||||
```
|
||||
|
||||
`pino.info({'hello': 'world'})`:
|
||||
|
||||
```
|
||||
|
||||
OBJECT benchmark averages
|
||||
BunyanObj average: 410.379ms
|
||||
WinstonObj average: 273.120ms
|
||||
BoleObj average: 185.069ms
|
||||
LogLevelObject average: 433.425ms
|
||||
PinoObj average: 119.315ms
|
||||
PinoMinLengthObj average: 76.968ms
|
||||
PinoNodeStreamObj average: 164.268ms
|
||||
|
||||
```
|
||||
|
||||
`pino.info(aBigDeeplyNestedObject)`:
|
||||
|
||||
```
|
||||
|
||||
DEEP-OBJECT benchmark averages
|
||||
BunyanDeepObj average: 1.839ms
|
||||
WinstonDeepObj average: 5.604ms
|
||||
BoleDeepObj average: 3.422ms
|
||||
LogLevelDeepObj average: 11.716ms
|
||||
PinoDeepObj average: 2.256ms
|
||||
PinoMinLengthDeepObj average: 2.240ms
|
||||
PinoNodeStreamDeepObj average: 2.595ms
|
||||
|
||||
```
|
||||
|
||||
`pino.info('hello %s %j %d', 'world', {obj: true}, 4, {another: 'obj'})`:
|
||||
|
||||
For a fair comparison, [LogLevel](http://npm.im/loglevel) was extended
|
||||
to include a timestamp and [bole](http://npm.im/bole) had
|
||||
`fastTime` mode switched on.
|
||||
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
# Browser API
|
||||
|
||||
Pino is compatible with [`browserify`](https://npm.im/browserify) for browser-side usage:
|
||||
|
||||
This can be useful with isomorphic/universal JavaScript code.
|
||||
|
||||
By default, in the browser,
|
||||
`pino` uses corresponding [Log4j](https://en.wikipedia.org/wiki/Log4j) `console` methods (`console.error`, `console.warn`, `console.info`, `console.debug`, `console.trace`) and uses `console.error` for any `fatal` level logs.
|
||||
|
||||
## Options
|
||||
|
||||
Pino can be passed a `browser` object in the options object,
|
||||
which can have the following properties:
|
||||
|
||||
### `asObject` (Boolean)
|
||||
|
||||
```js
|
||||
const pino = require('pino')({browser: {asObject: true}})
|
||||
```
|
||||
|
||||
The `asObject` option will create a pino-like log object instead of
|
||||
passing all arguments to a console method, for instance:
|
||||
|
||||
```js
|
||||
pino.info('hi') // creates and logs {msg: 'hi', level: 30, time: <ts>}
|
||||
```
|
||||
|
||||
When `write` is set, `asObject` will always be `true`.
|
||||
|
||||
### `formatters` (Object)
|
||||
|
||||
An object containing functions for formatting the shape of the log lines. When provided, it enables the logger to produce a pino-like log object with customized formatting. Currently, it supports formatting for the `level` object only.
|
||||
|
||||
##### `level`
|
||||
|
||||
Changes the shape of the log level. The default shape is `{ level: number }`.
|
||||
The function takes two arguments, the label of the level (e.g. `'info'`)
|
||||
and the numeric value (e.g. `30`).
|
||||
|
||||
```js
|
||||
const formatters = {
|
||||
level (label, number) {
|
||||
return { level: number }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### `write` (Function | Object)
|
||||
|
||||
Instead of passing log messages to `console.log` they can be passed to
|
||||
a supplied function.
|
||||
|
||||
If `write` is set to a single function, all logging objects are passed
|
||||
to this function.
|
||||
|
||||
```js
|
||||
const pino = require('pino')({
|
||||
browser: {
|
||||
write: (o) => {
|
||||
// do something with o
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
If `write` is an object, it can have methods that correspond to the
|
||||
levels. When a message is logged at a given level, the corresponding
|
||||
method is called. If a method isn't present, the logging falls back
|
||||
to using the `console`.
|
||||
|
||||
|
||||
```js
|
||||
const pino = require('pino')({
|
||||
browser: {
|
||||
write: {
|
||||
info: function (o) {
|
||||
//process info log object
|
||||
},
|
||||
error: function (o) {
|
||||
//process error log object
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `serialize`: (Boolean | Array)
|
||||
|
||||
The serializers provided to `pino` are ignored by default in the browser, including
|
||||
the standard serializers provided with Pino. Since the default destination for log
|
||||
messages is the console, values such as `Error` objects are enhanced for inspection,
|
||||
which they otherwise wouldn't be if the Error serializer was enabled.
|
||||
|
||||
We can turn all serializers on,
|
||||
|
||||
```js
|
||||
const pino = require('pino')({
|
||||
browser: {
|
||||
serialize: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Or we can selectively enable them via an array:
|
||||
|
||||
```js
|
||||
const pino = require('pino')({
|
||||
serializers: {
|
||||
custom: myCustomSerializer,
|
||||
another: anotherSerializer
|
||||
},
|
||||
browser: {
|
||||
serialize: ['custom']
|
||||
}
|
||||
})
|
||||
// following will apply myCustomSerializer to the custom property,
|
||||
// but will not apply anotherSerializer to another key
|
||||
pino.info({custom: 'a', another: 'b'})
|
||||
```
|
||||
|
||||
When `serialize` is `true` the standard error serializer is also enabled (see https://github.com/pinojs/pino/blob/master/docs/api.md#stdSerializers).
|
||||
This is a global serializer, which will apply to any `Error` objects passed to the logger methods.
|
||||
|
||||
If `serialize` is an array the standard error serializer is also automatically enabled, it can
|
||||
be explicitly disabled by including a string in the serialize array: `!stdSerializers.err`, like so:
|
||||
|
||||
```js
|
||||
const pino = require('pino')({
|
||||
serializers: {
|
||||
custom: myCustomSerializer,
|
||||
another: anotherSerializer
|
||||
},
|
||||
browser: {
|
||||
serialize: ['!stdSerializers.err', 'custom'] //will not serialize Errors, will serialize `custom` keys
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
The `serialize` array also applies to any child logger serializers (see https://github.com/pinojs/pino/blob/master/docs/api.md#discussion-2
|
||||
for how to set child-bound serializers).
|
||||
|
||||
Unlike server pino the serializers apply to every object passed to the logger method,
|
||||
if the `asObject` option is `true`, this results in the serializers applying to the
|
||||
first object (as in server pino).
|
||||
|
||||
For more info on serializers see https://github.com/pinojs/pino/blob/master/docs/api.md#parameters.
|
||||
|
||||
### `transmit` (Object)
|
||||
|
||||
An object with `send` and `level` properties.
|
||||
|
||||
The `transmit.level` property specifies the minimum level (inclusive) of when the `send` function
|
||||
should be called, if not supplied the `send` function be called based on the main logging `level`
|
||||
(set via `options.level`, defaulting to `info`).
|
||||
|
||||
The `transmit` object must have a `send` function which will be called after
|
||||
writing the log message. The `send` function is passed the level of the log
|
||||
message and a `logEvent` object.
|
||||
|
||||
The `logEvent` object is a data structure representing a log message, it represents
|
||||
the arguments passed to a logger statement, the level
|
||||
at which they were logged, and the hierarchy of child bindings.
|
||||
|
||||
The `logEvent` format is structured like so:
|
||||
|
||||
```js
|
||||
{
|
||||
ts = Number,
|
||||
messages = Array,
|
||||
bindings = Array,
|
||||
level: { label = String, value = Number}
|
||||
}
|
||||
```
|
||||
|
||||
The `ts` property is a Unix epoch timestamp in milliseconds, the time is taken from the moment the
|
||||
logger method is called.
|
||||
|
||||
The `messages` array is all arguments passed to logger method, (for instance `logger.info('a', 'b', 'c')`
|
||||
would result in `messages` array `['a', 'b', 'c']`).
|
||||
|
||||
The `bindings` array represents each child logger (if any), and the relevant bindings.
|
||||
For instance, given `logger.child({a: 1}).child({b: 2}).info({c: 3})`, the bindings array
|
||||
would hold `[{a: 1}, {b: 2}]` and the `messages` array would be `[{c: 3}]`. The `bindings`
|
||||
are ordered according to their position in the child logger hierarchy, with the lowest index
|
||||
being the top of the hierarchy.
|
||||
|
||||
By default, serializers are not applied to log output in the browser, but they will *always* be
|
||||
applied to `messages` and `bindings` in the `logEvent` object. This allows us to ensure a consistent
|
||||
format for all values between server and client.
|
||||
|
||||
The `level` holds the label (for instance `info`), and the corresponding numerical value
|
||||
(for instance `30`). This could be important in cases where client-side level values and
|
||||
labels differ from server-side.
|
||||
|
||||
The point of the `send` function is to remotely record log messages:
|
||||
|
||||
```js
|
||||
const pino = require('pino')({
|
||||
browser: {
|
||||
transmit: {
|
||||
level: 'warn',
|
||||
send: function (level, logEvent) {
|
||||
if (level === 'warn') {
|
||||
// maybe send the logEvent to a separate endpoint
|
||||
// or maybe analyze the messages further before sending
|
||||
}
|
||||
// we could also use the `logEvent.level.value` property to determine
|
||||
// numerical value
|
||||
if (logEvent.level.value >= 50) { // covers error and fatal
|
||||
|
||||
// send the logEvent somewhere
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `disabled` (Boolean)
|
||||
|
||||
```js
|
||||
const pino = require('pino')({browser: {disabled: true}})
|
||||
```
|
||||
|
||||
The `disabled` option will disable logging in browser if set
|
||||
to `true`, by default it is set to `false`.
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# Bundling
|
||||
|
||||
Due to its internal architecture based on Worker Threads, it is not possible to bundle Pino *without* generating additional files.
|
||||
|
||||
In particular, a bundler must ensure that the following files are also bundled separately:
|
||||
|
||||
* `lib/worker.js` from the `thread-stream` dependency
|
||||
* `file.js`
|
||||
* `lib/worker.js`
|
||||
* `lib/worker-pipeline.js`
|
||||
* Any transport used by the user (like `pino-pretty`)
|
||||
|
||||
Once the files above have been generated, the bundler must also add information about the files above by injecting a code that sets `__bundlerPathsOverrides` in the `globalThis` object.
|
||||
|
||||
The variable is an object whose keys are an identifier for the files and the values are the paths of files relative to the currently bundle files.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
// Inject this using your bundle plugin
|
||||
globalThis.__bundlerPathsOverrides = {
|
||||
'thread-stream-worker': pinoWebpackAbsolutePath('./thread-stream-worker.js')
|
||||
'pino/file': pinoWebpackAbsolutePath('./pino-file.js'),
|
||||
'pino-worker': pinoWebpackAbsolutePath('./pino-worker.js'),
|
||||
'pino-pipeline-worker': pinoWebpackAbsolutePath('./pino-pipeline-worker.js'),
|
||||
'pino-pretty': pinoWebpackAbsolutePath('./pino-pretty.js'),
|
||||
};
|
||||
```
|
||||
|
||||
Note that `pino/file`, `pino-worker`, `pino-pipeline-worker`, and `thread-stream-worker` are required identifiers. Other identifiers are possible based on the user configuration.
|
||||
|
||||
## Webpack Plugin
|
||||
|
||||
If you are a Webpack user, you can achieve this with [pino-webpack-plugin](https://github.com/pinojs/pino-webpack-plugin) without manual configuration of `__bundlerPathsOverrides`; however, you still need to configure it manually if you are using other bundlers.
|
||||
|
||||
## Esbuild Plugin
|
||||
|
||||
[esbuild-plugin-pino](https://github.com/davipon/esbuild-plugin-pino) is the esbuild plugin to generate extra pino files for bundling.
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
# Child loggers
|
||||
|
||||
Let's assume we want to have `"module":"foo"` added to every log within a
|
||||
module `foo.js`.
|
||||
|
||||
To accomplish this, simply use a child logger:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
// imports a pino logger instance of `require('pino')()`
|
||||
const parentLogger = require('./lib/logger')
|
||||
const log = parentLogger.child({module: 'foo'})
|
||||
|
||||
function doSomething () {
|
||||
log.info('doSomething invoked')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
doSomething
|
||||
}
|
||||
```
|
||||
|
||||
## Cost of child logging
|
||||
|
||||
Child logger creation is fast:
|
||||
|
||||
```
|
||||
benchBunyanCreation*10000: 564.514ms
|
||||
benchBoleCreation*10000: 283.276ms
|
||||
benchPinoCreation*10000: 258.745ms
|
||||
benchPinoExtremeCreation*10000: 150.506ms
|
||||
```
|
||||
|
||||
Logging through a child logger has little performance penalty:
|
||||
|
||||
```
|
||||
benchBunyanChild*10000: 556.275ms
|
||||
benchBoleChild*10000: 288.124ms
|
||||
benchPinoChild*10000: 231.695ms
|
||||
benchPinoExtremeChild*10000: 122.117ms
|
||||
```
|
||||
|
||||
Logging via the child logger of a child logger also has negligible overhead:
|
||||
|
||||
```
|
||||
benchBunyanChildChild*10000: 559.082ms
|
||||
benchPinoChildChild*10000: 229.264ms
|
||||
benchPinoExtremeChildChild*10000: 127.753ms
|
||||
```
|
||||
|
||||
## Duplicate keys caveat
|
||||
|
||||
Naming conflicts can arise between child loggers and
|
||||
children of child loggers.
|
||||
|
||||
This isn't as bad as it sounds, even if the same keys between
|
||||
parent and child loggers are used, Pino resolves the conflict in the sanest way.
|
||||
|
||||
For example, consider the following:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
pino(pino.destination('./my-log'))
|
||||
.child({a: 'property'})
|
||||
.child({a: 'prop'})
|
||||
.info('howdy')
|
||||
```
|
||||
|
||||
```sh
|
||||
$ cat my-log
|
||||
{"pid":95469,"hostname":"MacBook-Pro-3.home","level":30,"msg":"howdy","time":1459534114473,"a":"property","a":"prop"}
|
||||
```
|
||||
|
||||
Notice how there are two keys named `a` in the JSON output. The sub-child's properties
|
||||
appear after the parent child properties.
|
||||
|
||||
At some point, the logs will most likely be processed (for instance with a [transport](transports.md)),
|
||||
and this generally involves parsing. `JSON.parse` will return an object where the conflicting
|
||||
namespace holds the final value assigned to it:
|
||||
|
||||
```sh
|
||||
$ cat my-log | node -e "process.stdin.once('data', (line) => console.log(JSON.stringify(JSON.parse(line))))"
|
||||
{"pid":95469,"hostname":"MacBook-Pro-3.home","level":30,"msg":"howdy","time":"2016-04-01T18:08:34.473Z","a":"prop"}
|
||||
```
|
||||
|
||||
Ultimately the conflict is resolved by taking the last value, which aligns with Bunyan's child logging
|
||||
behavior.
|
||||
|
||||
There may be cases where this edge case becomes problematic if a JSON parser with alternative behavior
|
||||
is used to process the logs. It's recommended to be conscious of namespace conflicts with child loggers,
|
||||
in light of an expected log processing approach.
|
||||
|
||||
One of Pino's performance tricks is to avoid building objects and stringifying
|
||||
them, so we're building strings instead. This is why duplicate keys between
|
||||
parents and children will end up in the log output.
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
# Pino Ecosystem
|
||||
|
||||
This is a list of ecosystem modules that integrate with `pino`.
|
||||
|
||||
Modules listed under [Core](#core) are maintained by the Pino team. Modules
|
||||
listed under [Community](#community) are maintained by independent community
|
||||
members.
|
||||
|
||||
Please send a PR to add new modules!
|
||||
|
||||
<a id="core"></a>
|
||||
## Core
|
||||
|
||||
### Frameworks
|
||||
+ [`express-pino-logger`](https://github.com/pinojs/express-pino-logger): use
|
||||
Pino to log requests within [express](https://expressjs.com/).
|
||||
+ [`koa-pino-logger`](https://github.com/pinojs/koa-pino-logger): use Pino to
|
||||
log requests within [Koa](https://koajs.com/).
|
||||
+ [`rill-pino-logger`](https://github.com/pinojs/rill-pino-logger): use Pino as
|
||||
the logger for the [Rill framework](https://rill.site/).
|
||||
+ [`restify-pino-logger`](https://github.com/pinojs/restify-pino-logger): use
|
||||
Pino to log requests within [restify](http://restify.com/).
|
||||
|
||||
### Utilities
|
||||
+ [`pino-arborsculpture`](https://github.com/pinojs/pino-arborsculpture): change
|
||||
log levels at runtime.
|
||||
+ [`pino-caller`](https://github.com/pinojs/pino-caller): add callsite to the log line.
|
||||
+ [`pino-clf`](https://github.com/pinojs/pino-clf): reformat Pino logs into
|
||||
Common Log Format.
|
||||
+ [`pino-debug`](https://github.com/pinojs/pino-debug): use Pino to interpret
|
||||
[`debug`](https://npm.im/debug) logs.
|
||||
+ [`pino-elasticsearch`](https://github.com/pinojs/pino-elasticsearch): send
|
||||
Pino logs to an Elasticsearch instance.
|
||||
+ [`pino-eventhub`](https://github.com/pinojs/pino-eventhub): send Pino logs
|
||||
to an [Event Hub](https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-what-is-event-hubs).
|
||||
+ [`pino-filter`](https://github.com/pinojs/pino-filter): filter Pino logs in
|
||||
the same fashion as the [`debug`](https://npm.im/debug) module.
|
||||
+ [`pino-gelf`](https://github.com/pinojs/pino-gelf): reformat Pino logs into
|
||||
GELF format for Graylog.
|
||||
+ [`pino-hapi`](https://github.com/pinojs/hapi-pino): use Pino as the logger
|
||||
for [Hapi](https://hapijs.com/).
|
||||
+ [`pino-http`](https://github.com/pinojs/pino-http): easily use Pino to log
|
||||
requests with the core `http` module.
|
||||
+ [`pino-http-print`](https://github.com/pinojs/pino-http-print): reformat Pino
|
||||
logs into traditional [HTTPD](https://httpd.apache.org/) style request logs.
|
||||
+ [`pino-multi-stream`](https://github.com/pinojs/pino-multi-stream): send
|
||||
logs to multiple destination streams (slow!).
|
||||
+ [`pino-mongodb`](https://github.com/pinojs/pino-mongodb): store Pino logs
|
||||
in a MongoDB database.
|
||||
+ [`pino-noir`](https://github.com/pinojs/pino-noir): redact sensitive information
|
||||
in logs.
|
||||
+ [`pino-pretty`](https://github.com/pinojs/pino-pretty): basic prettifier to
|
||||
make log lines human-readable.
|
||||
+ [`pino-socket`](https://github.com/pinojs/pino-socket): send logs to TCP or UDP
|
||||
destinations.
|
||||
+ [`pino-std-serializers`](https://github.com/pinojs/pino-std-serializers): the
|
||||
core object serializers used within Pino.
|
||||
+ [`pino-syslog`](https://github.com/pinojs/pino-syslog): reformat Pino logs
|
||||
to standard syslog format.
|
||||
+ [`pino-tee`](https://github.com/pinojs/pino-tee): pipe Pino logs into files
|
||||
based upon log levels.
|
||||
+ [`pino-toke`](https://github.com/pinojs/pino-toke): reformat Pino logs
|
||||
according to a given format string.
|
||||
+ [`pino-test`](https://github.com/pinojs/pino-test): a set of utilities for
|
||||
verifying logs generated by the Pino logger.
|
||||
|
||||
|
||||
<a id="community"></a>
|
||||
## Community
|
||||
|
||||
+ [`pino-colada`](https://github.com/lrlna/pino-colada): cute ndjson formatter for pino.
|
||||
+ [`pino-fluentd`](https://github.com/davidedantonio/pino-fluentd): send Pino logs to Elasticsearch,
|
||||
MongoDB, and many [others](https://www.fluentd.org/dataoutputs) via Fluentd.
|
||||
+ [`pino-pretty-min`](https://github.com/unjello/pino-pretty-min): a minimal
|
||||
prettifier inspired by the [logrus](https://github.com/sirupsen/logrus) logger.
|
||||
+ [`pino-rotating-file`](https://github.com/homeaway/pino-rotating-file): a hapi-pino log transport for splitting logs into separate, automatically rotating files.
|
||||
+ [`cls-proxify`](https://github.com/keenondrums/cls-proxify): integration of pino and [CLS](https://github.com/jeff-lewis/cls-hooked). Useful for creating dynamically configured child loggers (e.g. with added trace ID) for each request.
|
||||
+ [`pino-tiny`](https://github.com/holmok/pino-tiny): a tiny (and extensible?) little log formatter for pino.
|
||||
+ [`pino-dev`](https://github.com/dnjstrom/pino-dev): simple prettifier for pino with built-in support for common ecosystem packages.
|
||||
+ [`@newrelic/pino-enricher`](https://github.com/newrelic/newrelic-node-log-extensions/blob/main/packages/pino-log-enricher): a log customization to add New Relic context to use [Logs In Context](https://docs.newrelic.com/docs/logs/logs-context/logs-in-context/)
|
||||
+ [`pino-lambda`](https://github.com/FormidableLabs/pino-lambda): log transport for cloudwatch support inside aws-lambda
|
||||
+ [`cloud-pine`](https://github.com/metcoder95/cloud-pine): transport that provides abstraction and compatibility with [`@google-cloud/logging`](https://www.npmjs.com/package/@google-cloud/logging).
|
||||
+ [`crawlee-pino`](https://github.com/imyelo/crawlee-pino): use Pino to log within Crawlee
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
# Help
|
||||
|
||||
* [Log rotation](#rotate)
|
||||
* [Reopening log files](#reopening)
|
||||
* [Saving to multiple files](#multiple)
|
||||
* [Log filtering](#filter-logs)
|
||||
* [Transports and systemd](#transport-systemd)
|
||||
* [Log to different streams](#multi-stream)
|
||||
* [Duplicate keys](#dupe-keys)
|
||||
* [Log levels as labels instead of numbers](#level-string)
|
||||
* [Pino with `debug`](#debug)
|
||||
* [Unicode and Windows terminal](#windows)
|
||||
* [Mapping Pino Log Levels to Google Cloud Logging (Stackdriver) Severity Levels](#stackdriver)
|
||||
* [Using Grafana Loki to evaluate pino logs in a kubernetes cluster](#grafana-loki)
|
||||
* [Avoid Message Conflict](#avoid-message-conflict)
|
||||
* [Best performance for logging to `stdout`](#best-performance-for-stdout)
|
||||
* [Testing](#testing)
|
||||
|
||||
<a id="rotate"></a>
|
||||
## Log rotation
|
||||
|
||||
Use a separate tool for log rotation:
|
||||
We recommend [logrotate](https://github.com/logrotate/logrotate).
|
||||
Consider we output our logs to `/var/log/myapp.log` like so:
|
||||
|
||||
```
|
||||
$ node server.js > /var/log/myapp.log
|
||||
```
|
||||
|
||||
We would rotate our log files with logrotate, by adding the following to `/etc/logrotate.d/myapp`:
|
||||
|
||||
```
|
||||
/var/log/myapp.log {
|
||||
su root
|
||||
daily
|
||||
rotate 7
|
||||
delaycompress
|
||||
compress
|
||||
notifempty
|
||||
missingok
|
||||
copytruncate
|
||||
}
|
||||
```
|
||||
|
||||
The `copytruncate` configuration has a very slight possibility of lost log lines due
|
||||
to a gap between copying and truncating - the truncate may occur after additional lines
|
||||
have been written. To perform log rotation without `copytruncate`, see the [Reopening log files](#reopening)
|
||||
help.
|
||||
|
||||
<a id="reopening"></a>
|
||||
## Reopening log files
|
||||
|
||||
In cases where a log rotation tool doesn't offer copy-truncate capabilities,
|
||||
or where using them is deemed inappropriate, `pino.destination`
|
||||
can reopen file paths after a file has been moved away.
|
||||
|
||||
One way to use this is to set up a `SIGUSR2` or `SIGHUP` signal handler that
|
||||
reopens the log file destination, making sure to write the process PID out
|
||||
somewhere so the log rotation tool knows where to send the signal.
|
||||
|
||||
```js
|
||||
// write the process pid to a well known location for later
|
||||
const fs = require('fs')
|
||||
fs.writeFileSync('/var/run/myapp.pid', process.pid)
|
||||
|
||||
const dest = pino.destination('/log/file')
|
||||
const logger = require('pino')(dest)
|
||||
process.on('SIGHUP', () => dest.reopen())
|
||||
```
|
||||
|
||||
The log rotation tool can then be configured to send this signal to the process
|
||||
after a log rotation event has occurred.
|
||||
|
||||
Given a similar scenario as in the [Log rotation](#rotate) section a basic
|
||||
`logrotate` config that aligns with this strategy would look similar to the following:
|
||||
|
||||
```
|
||||
/var/log/myapp.log {
|
||||
su root
|
||||
daily
|
||||
rotate 7
|
||||
delaycompress
|
||||
compress
|
||||
notifempty
|
||||
missingok
|
||||
postrotate
|
||||
kill -HUP `cat /var/run/myapp.pid`
|
||||
endscript
|
||||
}
|
||||
```
|
||||
|
||||
<a id="multiple"></a>
|
||||
## Saving to multiple files
|
||||
|
||||
See [`pino.multistream`](/docs/api.md#pino-multistream).
|
||||
|
||||
<a id="filter-logs"></a>
|
||||
## Log Filtering
|
||||
The Pino philosophy advocates common, preexisting, system utilities.
|
||||
|
||||
Some recommendations in line with this philosophy are:
|
||||
|
||||
1. Use [`grep`](https://linux.die.net/man/1/grep):
|
||||
```sh
|
||||
$ # View all "INFO" level logs
|
||||
$ node app.js | grep '"level":30'
|
||||
```
|
||||
1. Use [`jq`](https://stedolan.github.io/jq/):
|
||||
```sh
|
||||
$ # View all "ERROR" level logs
|
||||
$ node app.js | jq 'select(.level == 50)'
|
||||
```
|
||||
|
||||
<a id="transport-systemd"></a>
|
||||
## Transports and systemd
|
||||
`systemd` makes it complicated to use pipes in services. One method for overcoming
|
||||
this challenge is to use a subshell:
|
||||
|
||||
```
|
||||
ExecStart=/bin/sh -c '/path/to/node app.js | pino-transport'
|
||||
```
|
||||
|
||||
<a id="multi-stream"></a>
|
||||
## Log to different streams
|
||||
|
||||
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.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')
|
||||
var streams = [
|
||||
{level: 'debug', stream: process.stdout},
|
||||
{level: 'error', stream: process.stderr},
|
||||
{level: 'fatal', stream: process.stderr}
|
||||
]
|
||||
|
||||
const logger = pino({
|
||||
name: 'my-app',
|
||||
level: 'debug', // must be the lowest level of all streams
|
||||
}, pino.multistream(streams))
|
||||
```
|
||||
|
||||
<a id="dupe-keys"></a>
|
||||
## How Pino handles duplicate keys
|
||||
|
||||
Duplicate keys are possibly when a child logger logs an object with a key that
|
||||
collides with a key in the child loggers bindings.
|
||||
|
||||
See the [child logger duplicate keys caveat](/docs/child-loggers.md#duplicate-keys-caveat)
|
||||
for information on this is handled.
|
||||
|
||||
<a id="level-string"></a>
|
||||
## Log levels as labels instead of numbers
|
||||
Pino log lines are meant to be parsable. Thus, Pino's default mode of operation
|
||||
is to print the level value instead of the string name.
|
||||
However, you can use the [`formatters`](/docs/api.md#formatters-object) option
|
||||
with a [`level`](/docs/api.md#level) function to print the string name instead of the level value :
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
|
||||
const log = pino({
|
||||
formatters: {
|
||||
level: (label) => {
|
||||
return {
|
||||
level: label
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
log.info('message')
|
||||
|
||||
// {"level":"info","time":1661632832200,"pid":18188,"hostname":"foo","msg":"message"}
|
||||
```
|
||||
|
||||
Although it works, we recommend using one of these options instead if you are able:
|
||||
|
||||
1. If the only change desired is the name then a transport can be used. One such
|
||||
transport is [`pino-text-level-transport`](https://npm.im/pino-text-level-transport).
|
||||
1. Use a prettifier like [`pino-pretty`](https://npm.im/pino-pretty) to make
|
||||
the logs human friendly.
|
||||
|
||||
<a id="debug"></a>
|
||||
## Pino with `debug`
|
||||
|
||||
The popular [`debug`](https://npm.im/debug) is used in many modules across the ecosystem.
|
||||
|
||||
The [`pino-debug`](https://github.com/pinojs/pino-debug) module
|
||||
can capture calls to `debug` loggers and run them
|
||||
through `pino` instead. This results in a 10x (20x in asynchronous mode)
|
||||
performance improvement - even though `pino-debug` is logging additional
|
||||
data and wrapping it in JSON.
|
||||
|
||||
To quickly enable this install [`pino-debug`](https://github.com/pinojs/pino-debug)
|
||||
and preload it with the `-r` flag, enabling any `debug` logs with the
|
||||
`DEBUG` environment variable:
|
||||
|
||||
```sh
|
||||
$ npm i pino-debug
|
||||
$ DEBUG=* node -r pino-debug app.js
|
||||
```
|
||||
|
||||
[`pino-debug`](https://github.com/pinojs/pino-debug) also offers fine-grain control to map specific `debug`
|
||||
namespaces to `pino` log levels. See [`pino-debug`](https://github.com/pinojs/pino-debug)
|
||||
for more.
|
||||
|
||||
<a id="windows"></a>
|
||||
## Unicode and Windows terminal
|
||||
|
||||
Pino uses [sonic-boom](https://github.com/mcollina/sonic-boom) to speed
|
||||
up logging. Internally, it uses [`fs.write`](https://nodejs.org/dist/latest-v10.x/docs/api/fs.html#fs_fs_write_fd_string_position_encoding_callback) to write log lines directly to a file
|
||||
descriptor. On Windows, Unicode output is not handled properly in the
|
||||
terminal (both `cmd.exe` and PowerShell), and as such the output could
|
||||
be visualized incorrectly if the log lines include utf8 characters. It
|
||||
is possible to configure the terminal to visualize those characters
|
||||
correctly with the use of [`chcp`](https://ss64.com/nt/chcp.html) by
|
||||
executing in the terminal `chcp 65001`. This is a known limitation of
|
||||
Node.js.
|
||||
|
||||
<a id="stackdriver"></a>
|
||||
## Mapping Pino Log Levels to Google Cloud Logging (Stackdriver) Severity Levels
|
||||
|
||||
Google Cloud Logging uses `severity` levels instead of log levels. As a result, all logs may show as INFO
|
||||
level logs while completely ignoring the level set in the pino log. Google Cloud Logging also prefers that
|
||||
log data is present inside a `message` key instead of the default `msg` key that Pino uses. Use a technique
|
||||
similar to the one below to retain log levels in Google Cloud Logging
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
|
||||
// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity
|
||||
const PinoLevelToSeverityLookup = {
|
||||
trace: 'DEBUG',
|
||||
debug: 'DEBUG',
|
||||
info: 'INFO',
|
||||
warn: 'WARNING',
|
||||
error: 'ERROR',
|
||||
fatal: 'CRITICAL',
|
||||
};
|
||||
|
||||
const defaultPinoConf = {
|
||||
messageKey: 'message',
|
||||
formatters: {
|
||||
level(label, number) {
|
||||
return {
|
||||
severity: PinoLevelToSeverityLookup[label] || PinoLevelToSeverityLookup['info'],
|
||||
level: number,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = function createLogger(options) {
|
||||
return pino(Object.assign({}, options, defaultPinoConf))
|
||||
}
|
||||
```
|
||||
|
||||
<a id="grafana-loki"></a>
|
||||
## Using Grafana Loki to evaluate pino logs in a kubernetes cluster
|
||||
|
||||
To get pino logs into Grafana Loki there are two options:
|
||||
|
||||
1. **Push:** Use [pino-loki](https://github.com/Julien-R44/pino-loki) to send logs directly to Loki.
|
||||
1. **Pull:** Configure Grafana Promtail to read and properly parse the logs before sending them to Loki.
|
||||
Similar to Google Cloud logging, this involves remapping the log levels. See this [article](https://medium.com/@janpaepke/structured-logging-in-the-grafana-monitoring-stack-8aff0a5af2f5) for details.
|
||||
|
||||
<a id="avoid-message-conflict"></a>
|
||||
## Avoid Message Conflict
|
||||
|
||||
As described in the [`message` documentation](/docs/api.md#message), when a log
|
||||
is written like `log.info({ msg: 'a message' }, 'another message')` then the
|
||||
final output JSON will have `"msg":"another message"` and the `'a message'`
|
||||
string will be lost. To overcome this, the [`logMethod` hook](/docs/api.md#logmethod)
|
||||
can be used:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const log = require('pino')({
|
||||
level: 'debug',
|
||||
hooks: {
|
||||
logMethod (inputArgs, method) {
|
||||
if (inputArgs.length === 2 && inputArgs[0].msg) {
|
||||
inputArgs[0].originalMsg = inputArgs[0].msg
|
||||
}
|
||||
return method.apply(this, inputArgs)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
log.info('no original message')
|
||||
log.info({ msg: 'mapped to originalMsg' }, 'a message')
|
||||
|
||||
// {"level":30,"time":1596313323106,"pid":63739,"hostname":"foo","msg":"no original message"}
|
||||
// {"level":30,"time":1596313323107,"pid":63739,"hostname":"foo","msg":"a message","originalMsg":"mapped to originalMsg"}
|
||||
```
|
||||
|
||||
<a id="best-performance-for-stdout"></a>
|
||||
## Best performance for logging to `stdout`
|
||||
|
||||
The best performance for logging directly to stdout is _usually_ achieved by using the
|
||||
default configuration:
|
||||
|
||||
```js
|
||||
const log = require('pino')();
|
||||
```
|
||||
|
||||
You should only have to configure custom transports or other settings
|
||||
if you have broader logging requirements.
|
||||
|
||||
<a id="testing"></a>
|
||||
## Testing
|
||||
|
||||
See [`pino-test`](https://github.com/pinojs/pino-test).
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
## Long Term Support
|
||||
|
||||
Pino's Long Term Support (LTS) is provided according to the schedule laid
|
||||
out in this document:
|
||||
|
||||
1. Major releases, "X" release of [semantic versioning][semver] X.Y.Z release
|
||||
versions, are supported for a minimum period of six months from their release
|
||||
date. The release date of any specific version can be found at
|
||||
[https://github.com/pinojs/pino/releases](https://github.com/pinojs/pino/releases).
|
||||
|
||||
1. Major releases will receive security updates for an additional six months
|
||||
from the release of the next major release. After this period
|
||||
we will still review and release security fixes as long as they are
|
||||
provided by the community and they do not violate other constraints,
|
||||
e.g. minimum supported Node.js version.
|
||||
|
||||
1. Major releases will be tested and verified against all Node.js
|
||||
release lines that are supported by the
|
||||
[Node.js LTS policy](https://github.com/nodejs/Release) within the
|
||||
LTS period of that given Pino release line. This implies that only
|
||||
the latest Node.js release of a given line is supported.
|
||||
|
||||
A "month" is defined as 30 consecutive days.
|
||||
|
||||
> ## Security Releases and Semver
|
||||
>
|
||||
> As a consequence of providing long-term support for major releases, there
|
||||
> are occasions where we need to release breaking changes as a _minor_
|
||||
> version release. Such changes will _always_ be noted in the
|
||||
> [release notes](https://github.com/pinojs/pino/releases).
|
||||
>
|
||||
> To avoid automatically receiving breaking security updates it is possible to use
|
||||
> the tilde (`~`) range qualifier. For example, to get patches for the 6.1
|
||||
> release, and avoid automatically updating to the 6.1 release, specify
|
||||
> the dependency as `"pino": "~6.1.x"`. This will leave your application vulnerable,
|
||||
> so please use with caution.
|
||||
|
||||
[semver]: https://semver.org/
|
||||
|
||||
<a name="lts-schedule"></a>
|
||||
|
||||
### Schedule
|
||||
|
||||
| Version | Release Date | End Of LTS Date | Node.js |
|
||||
| :------ | :----------- | :-------------- | :------------------- |
|
||||
| 8.x | 2022-06-01 | TBD | 14, 16, 18 |
|
||||
| 7.x | 2021-10-14 | 2023-06-01 | 12, 14, 16 |
|
||||
| 6.x | 2020-03-07 | 2022-04-14 | 10, 12, 14, 16 |
|
||||
|
||||
<a name="supported-os"></a>
|
||||
|
||||
### CI tested operating systems
|
||||
|
||||
Pino uses GitHub Actions for CI testing, please refer to
|
||||
[GitHub's documentation regarding workflow runners](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources)
|
||||
for further details on what the latest virtual environment is in relation to
|
||||
the YAML workflow labels below:
|
||||
|
||||
| OS | YAML Workflow Label | Node.js |
|
||||
|---------|------------------------|--------------|
|
||||
| Linux | `ubuntu-latest` | 14,16,18 |
|
||||
| Windows | `windows-latest` | 14,16,18 |
|
||||
| MacOS | `macos-latest` | 14,16,18 |
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# Pretty Printing
|
||||
|
||||
By default, Pino log lines are newline delimited JSON (NDJSON). This is perfect
|
||||
for production usage and long-term storage. It's not so great for development
|
||||
environments. Thus, Pino logs can be prettified by using a Pino prettifier
|
||||
module like [`pino-pretty`][pp]:
|
||||
|
||||
1. Install a prettifier module as a separate dependency, e.g. `npm install pino-pretty`.
|
||||
2. Instantiate the logger with the `transport.target` option set to `'pino-pretty'`:
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const logger = pino({
|
||||
transport: {
|
||||
target: 'pino-pretty'
|
||||
},
|
||||
})
|
||||
|
||||
logger.info('hi')
|
||||
```
|
||||
3. The transport option can also have an options object containing `pino-pretty` options:
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const logger = pino({
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.info('hi')
|
||||
```
|
||||
|
||||
[pp]: https://github.com/pinojs/pino-pretty
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
# Redaction
|
||||
|
||||
> Redaction is not supported in the browser [#670](https://github.com/pinojs/pino/issues/670)
|
||||
|
||||
To redact sensitive information, supply paths to keys that hold sensitive data
|
||||
using the `redact` option. Note that paths that contain hyphens need to use
|
||||
brackets to access the hyphenated property:
|
||||
|
||||
```js
|
||||
const logger = require('.')({
|
||||
redact: ['key', 'path.to.key', 'stuff.thats[*].secret', 'path["with-hyphen"]']
|
||||
})
|
||||
|
||||
logger.info({
|
||||
key: 'will be redacted',
|
||||
path: {
|
||||
to: {key: 'sensitive', another: 'thing'}
|
||||
},
|
||||
stuff: {
|
||||
thats: [
|
||||
{secret: 'will be redacted', logme: 'will be logged'},
|
||||
{secret: 'as will this', logme: 'as will this'}
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
This will output:
|
||||
|
||||
```JSON
|
||||
{"level":30,"time":1527777350011,"pid":3186,"hostname":"Davids-MacBook-Pro-3.local","key":"[Redacted]","path":{"to":{"key":"[Redacted]","another":"thing"}},"stuff":{"thats":[{"secret":"[Redacted]","logme":"will be logged"},{"secret":"[Redacted]","logme":"as will this"}]}}
|
||||
```
|
||||
|
||||
The `redact` option can take an array (as shown in the above example) or
|
||||
an object. This allows control over *how* information is redacted.
|
||||
|
||||
For instance, setting the censor:
|
||||
|
||||
```js
|
||||
const logger = require('.')({
|
||||
redact: {
|
||||
paths: ['key', 'path.to.key', 'stuff.thats[*].secret'],
|
||||
censor: '**GDPR COMPLIANT**'
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({
|
||||
key: 'will be redacted',
|
||||
path: {
|
||||
to: {key: 'sensitive', another: 'thing'}
|
||||
},
|
||||
stuff: {
|
||||
thats: [
|
||||
{secret: 'will be redacted', logme: 'will be logged'},
|
||||
{secret: 'as will this', logme: 'as will this'}
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
This will output:
|
||||
|
||||
```JSON
|
||||
{"level":30,"time":1527778563934,"pid":3847,"hostname":"Davids-MacBook-Pro-3.local","key":"**GDPR COMPLIANT**","path":{"to":{"key":"**GDPR COMPLIANT**","another":"thing"}},"stuff":{"thats":[{"secret":"**GDPR COMPLIANT**","logme":"will be logged"},{"secret":"**GDPR COMPLIANT**","logme":"as will this"}]}}
|
||||
```
|
||||
|
||||
The `redact.remove` option also allows for the key and value to be removed from output:
|
||||
|
||||
```js
|
||||
const logger = require('.')({
|
||||
redact: {
|
||||
paths: ['key', 'path.to.key', 'stuff.thats[*].secret'],
|
||||
remove: true
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({
|
||||
key: 'will be redacted',
|
||||
path: {
|
||||
to: {key: 'sensitive', another: 'thing'}
|
||||
},
|
||||
stuff: {
|
||||
thats: [
|
||||
{secret: 'will be redacted', logme: 'will be logged'},
|
||||
{secret: 'as will this', logme: 'as will this'}
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
This will output
|
||||
|
||||
```JSON
|
||||
{"level":30,"time":1527782356751,"pid":5758,"hostname":"Davids-MacBook-Pro-3.local","path":{"to":{"another":"thing"}},"stuff":{"thats":[{"logme":"will be logged"},{"logme":"as will this"}]}}
|
||||
```
|
||||
|
||||
See [pino options in API](/docs/api.md#redact-array-object) for `redact` API details.
|
||||
|
||||
<a name="paths"></a>
|
||||
## Path Syntax
|
||||
|
||||
The syntax for paths supplied to the `redact` option conform to the syntax in path lookups
|
||||
in standard ECMAScript, with two additions:
|
||||
|
||||
* paths may start with bracket notation
|
||||
* paths may contain the asterisk `*` to denote a wildcard
|
||||
* paths are **case sensitive**
|
||||
|
||||
By way of example, the following are all valid paths:
|
||||
|
||||
* `a.b.c`
|
||||
* `a["b-c"].d`
|
||||
* `["a-b"].c`
|
||||
* `a.b.*`
|
||||
* `a[*].b`
|
||||
|
||||
## Overhead
|
||||
|
||||
Pino's redaction functionality is built on top of [`fast-redact`](https://github.com/davidmarkclements/fast-redact)
|
||||
which adds about 2% overhead to `JSON.stringify` when using paths without wildcards.
|
||||
|
||||
When used with pino logger with a single redacted path, any overhead is within noise -
|
||||
a way to deterministically measure its effect has not been found. This is because it is not a bottleneck.
|
||||
|
||||
However, wildcard redaction does carry a non-trivial cost relative to explicitly declaring the keys
|
||||
(50% in a case where four keys are redacted across two objects). See
|
||||
the [`fast-redact` benchmarks](https://github.com/davidmarkclements/fast-redact#benchmarks) for details.
|
||||
|
||||
## Safety
|
||||
|
||||
The `redact` option is intended as an initialization time configuration option.
|
||||
Path strings must not originate from user input.
|
||||
The `fast-redact` module uses a VM context to syntax check the paths, user input
|
||||
should never be combined with such an approach. See the [`fast-redact` Caveat](https://github.com/davidmarkclements/fast-redact#caveat)
|
||||
and the [`fast-redact` Approach](https://github.com/davidmarkclements/fast-redact#approach) for in-depth information.
|
||||
+1092
@@ -0,0 +1,1092 @@
|
||||
# Transports
|
||||
|
||||
Pino transports can be used for both transmitting and transforming log output.
|
||||
|
||||
The way Pino generates logs:
|
||||
|
||||
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 separate thread or a separate process.
|
||||
|
||||
Before Pino v7 transports would ideally operate in a separate 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.
|
||||
In this case the transports would always operate asynchronously, and logs would be
|
||||
flushed as quickly as possible (there is nothing to do).
|
||||
|
||||
[worker-thread]: https://nodejs.org/dist/latest-v14.x/docs/api/worker_threads.html
|
||||
|
||||
## v7+ Transports
|
||||
|
||||
A transport is a module that exports a default function that returns a writable stream:
|
||||
|
||||
```js
|
||||
import { createWriteStream } from 'fs'
|
||||
|
||||
export default (options) => {
|
||||
return createWriteStream(options.destination)
|
||||
}
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
We would set up our transport by creating a transport stream with `pino.transport`
|
||||
and passing it to the `pino` function:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: '/absolute/path/to/my-transport.mjs'
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
The exported function can also be async. If we use an async function we can throw early
|
||||
if the transform could not be opened. As an example:
|
||||
|
||||
```js
|
||||
import fs from 'fs'
|
||||
import { once } from 'events'
|
||||
export default async (options) => {
|
||||
const stream = fs.createWriteStream(options.destination)
|
||||
await once(stream, 'open')
|
||||
return stream
|
||||
}
|
||||
```
|
||||
|
||||
While initializing the stream we're able to use `await` to perform asynchronous operations. In this
|
||||
case, waiting for the write streams `open` event.
|
||||
|
||||
Let's imagine the above was published to npm with the module name `some-file-transport`.
|
||||
|
||||
The `options.destination` value can be set when 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)
|
||||
```
|
||||
|
||||
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)serialize 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({
|
||||
targets: [
|
||||
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
|
||||
{ target: 'some-file-transport', options: { destination: '/dev/null' }}
|
||||
]
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
If we're using custom levels, they should be passed in when using more than one transport.
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
targets: [
|
||||
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
|
||||
{ target: 'some-file-transport', options: { destination: '/dev/null' }
|
||||
],
|
||||
levels: { foo: 35 }
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
It is also possible to use the `dedupe` option to send logs only to the stream with the higher level.
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
targets: [
|
||||
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
|
||||
{ target: 'some-file-transport', options: { destination: '/dev/null' }
|
||||
],
|
||||
dedupe: true
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
For more details on `pino.transport` see the [API docs for `pino.transport`][pino-transport].
|
||||
|
||||
[pino-transport]: /docs/api.md#pino-transport
|
||||
[sca]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
|
||||
|
||||
<a id="writing"></a>
|
||||
### 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 an async iterator with ESM:
|
||||
|
||||
```js
|
||||
import build from 'pino-abstract-transport'
|
||||
import SonicBoom from 'sonic-boom'
|
||||
import { once } from 'events'
|
||||
|
||||
export default async function (opts) {
|
||||
// SonicBoom is necessary to avoid loops with the main thread.
|
||||
// It is the same of pino.destination().
|
||||
const destination = new SonicBoom({ dest: opts.destination || 1, sync: false })
|
||||
await once(destination, 'ready')
|
||||
|
||||
return build(async function (source) {
|
||||
for await (let obj of source) {
|
||||
const toDrain = !destination.write(obj.msg.toUpperCase() + '\n')
|
||||
// This block will handle backpressure
|
||||
if (toDrain) {
|
||||
await once(destination, 'drain')
|
||||
}
|
||||
}
|
||||
}, {
|
||||
async close (err) {
|
||||
destination.end()
|
||||
await once(destination, 'close')
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
or using Node.js streams and CommonJS:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const build = require('pino-abstract-transport')
|
||||
const SonicBoom = require('sonic-boom')
|
||||
|
||||
module.exports = function (opts) {
|
||||
const destination = new SonicBoom({ dest: opts.destination || 1, sync: false })
|
||||
return build(function (source) {
|
||||
source.pipe(destination)
|
||||
}, {
|
||||
close (err, cb) {
|
||||
destination.end()
|
||||
destination.on('close', cb.bind(null, err))
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
(It is possible to use the async iterators with CommonJS and streams with ESM.)
|
||||
|
||||
To consume async iterators in batches, consider using the [hwp](https://github.com/mcollina/hwp) library.
|
||||
|
||||
The `close()` function is needed to make sure that the stream is closed and flushed when its
|
||||
callback is called or the returned promise resolves. Otherwise, log lines will be lost.
|
||||
|
||||
### Writing to a custom transport & stdout
|
||||
|
||||
In case you want to both use a custom transport, and output the log entries with default processing to STDOUT, you can use 'pino/file' transport configured with `destination: 1`:
|
||||
|
||||
```js
|
||||
const transports = [
|
||||
{
|
||||
target: 'pino/file',
|
||||
options: { destination: 1 } // this writes to STDOUT
|
||||
},
|
||||
{
|
||||
target: 'my-custom-transport',
|
||||
options: { someParameter: true }
|
||||
}
|
||||
]
|
||||
|
||||
const logger = pino(pino.transport({ targets: transports })
|
||||
```
|
||||
|
||||
### Creating a transport pipeline
|
||||
|
||||
As an example, the following transport returns a `Transform` stream:
|
||||
|
||||
```js
|
||||
import build from 'pino-abstract-transport'
|
||||
import { pipeline, Transform } from 'stream'
|
||||
export default async function (options) {
|
||||
return build(function (source) {
|
||||
const myTransportStream = new Transform({
|
||||
// Make sure autoDestroy is set,
|
||||
// this is needed in Node v12 or when using the
|
||||
// readable-stream module.
|
||||
autoDestroy: true,
|
||||
|
||||
objectMode: true,
|
||||
transform (chunk, enc, cb) {
|
||||
|
||||
// modifies the payload somehow
|
||||
chunk.service = 'pino'
|
||||
|
||||
// stringify the payload again
|
||||
this.push(`${JSON.stringify(chunk)}\n`)
|
||||
cb()
|
||||
}
|
||||
})
|
||||
pipeline(source, myTransportStream, () => {})
|
||||
return myTransportStream
|
||||
}, {
|
||||
// This is needed to be able to pipeline transports.
|
||||
enablePipelining: true
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Then you can pipeline them with:
|
||||
|
||||
```js
|
||||
import pino from 'pino'
|
||||
|
||||
const logger = pino({
|
||||
transport: {
|
||||
pipeline: [{
|
||||
target: './my-transform.js'
|
||||
}, {
|
||||
// Use target: 'pino/file' with STDOUT descriptor 1 to write
|
||||
// logs without any change.
|
||||
target: 'pino/file',
|
||||
options: { destination: 1 }
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
logger.info('hello world')
|
||||
```
|
||||
|
||||
__NOTE: there is no "default" destination for a pipeline but
|
||||
a terminating target, i.e. a `Writable` stream.__
|
||||
|
||||
### TypeScript compatibility
|
||||
|
||||
Pino provides basic support for transports written in TypeScript.
|
||||
|
||||
Ideally, they should be transpiled to ensure maximum compatibility, but sometimes
|
||||
you might want to use tools such as TS-Node, to execute your TypeScript
|
||||
code without having to go through an explicit transpilation step.
|
||||
|
||||
You can use your TypeScript code without explicit transpilation, but there are
|
||||
some known caveats:
|
||||
- For "pure" TypeScript code, ES imports are still not supported (ES imports are
|
||||
supported once the code is transpiled).
|
||||
- Only TS-Node is supported for now, there's no TSM support.
|
||||
- Running transports TypeScript code on TS-Node seems to be problematic on
|
||||
Windows systems, there's no official support for that yet.
|
||||
|
||||
### 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)
|
||||
```
|
||||
|
||||
By default, the `pino/file` transport assumes the directory of the destination file exists. If it does not exist, the transport will throw an error when it attempts to open the file for writing. The `mkdir` option may be set to `true` to configure the transport to create the directory, if it does not exist, before opening the file for writing.
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file',
|
||||
options: { destination: '/path/to/file', mkdir: true }
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
By default, the `pino/file` transport appends to the destination file if it exists. The `append` option may be set to `false` to configure the transport to truncate the file upon opening it for writing.
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file',
|
||||
options: { destination: '/path/to/file', append: false }
|
||||
})
|
||||
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. If `options.destination` is a string integer, e.g. `'1'`, it will be coerced to a number and used as a file descriptor. If this is not desired, provide a full path, e.g. `/tmp/1`.
|
||||
|
||||
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
|
||||
|
||||
The new transports boot asynchronously and calling `process.exit()` before the transport
|
||||
starts will cause logs to not be delivered.
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
targets: [
|
||||
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
|
||||
{ target: 'some-file-transport', options: { destination: '/dev/null' } }
|
||||
]
|
||||
})
|
||||
const logger = pino(transport)
|
||||
|
||||
logger.info('hello')
|
||||
|
||||
// If logs are printed before the transport is ready when process.exit(0) is called,
|
||||
// they will be lost.
|
||||
transport.on('ready', function () {
|
||||
process.exit(0)
|
||||
})
|
||||
```
|
||||
|
||||
## Legacy Transports
|
||||
|
||||
A legacy Pino "transport" is a supplementary tool that 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:
|
||||
|
||||
```sh
|
||||
node my-app-which-logs-stuff-to-stdout.js | node my-transport-process.js
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
PRs to this document are welcome for any new transports!
|
||||
|
||||
### Pino v7+ Compatible
|
||||
|
||||
+ [@logtail/pino](#@logtail/pino)
|
||||
+ [pino-elasticsearch](#pino-elasticsearch)
|
||||
+ [pino-pretty](#pino-pretty)
|
||||
+ [pino-loki](#pino-loki)
|
||||
+ [pino-seq-transport](#pino-seq-transport)
|
||||
+ [pino-sentry-transport](#pino-sentry-transport)
|
||||
+ [pino-airbrake-transport](#pino-airbrake-transport)
|
||||
+ [pino-datadog-transport](#pino-datadog-transport)
|
||||
+ [pino-slack-webhook](#pino-slack-webhook)
|
||||
+ [pino-axiom](#pino-axiom)
|
||||
+ [pino-opentelemetry-transport](#pino-opentelemetry-transport)
|
||||
+ [@axiomhq/pino](#@axiomhq/pino)
|
||||
+ [pino-discord-webhook](#pino-discord-webhook)
|
||||
+ [pino-logfmt](#pino-logfmt)
|
||||
|
||||
### Legacy
|
||||
|
||||
+ [pino-applicationinsights](#pino-applicationinsights)
|
||||
+ [pino-azuretable](#pino-azuretable)
|
||||
+ [pino-cloudwatch](#pino-cloudwatch)
|
||||
+ [pino-couch](#pino-couch)
|
||||
+ [pino-datadog](#pino-datadog)
|
||||
+ [pino-gelf](#pino-gelf)
|
||||
+ [pino-http-send](#pino-http-send)
|
||||
+ [pino-kafka](#pino-kafka)
|
||||
+ [pino-logdna](#pino-logdna)
|
||||
+ [pino-logflare](#pino-logflare)
|
||||
+ [pino-loki](#pino-loki)
|
||||
+ [pino-mq](#pino-mq)
|
||||
+ [pino-mysql](#pino-mysql)
|
||||
+ [pino-papertrail](#pino-papertrail)
|
||||
+ [pino-pg](#pino-pg)
|
||||
+ [pino-redis](#pino-redis)
|
||||
+ [pino-sentry](#pino-sentry)
|
||||
+ [pino-seq](#pino-seq)
|
||||
+ [pino-socket](#pino-socket)
|
||||
+ [pino-stackdriver](#pino-stackdriver)
|
||||
+ [pino-syslog](#pino-syslog)
|
||||
+ [pino-websocket](#pino-websocket)
|
||||
|
||||
|
||||
<a id="@logtail/pino"></a>
|
||||
### @logtail/pino
|
||||
|
||||
The [@logtail/pino](https://www.npmjs.com/package/@logtail/pino) NPM package is a transport that forwards logs to [Logtail](https://logtail.com) by [Better Stack](https://betterstack.com).
|
||||
|
||||
[Quick start guide ⇗](https://betterstack.com/docs/logs/javascript/pino)
|
||||
|
||||
<a id="pino-applicationinsights"></a>
|
||||
### pino-applicationinsights
|
||||
The [pino-applicationinsights](https://www.npmjs.com/package/pino-applicationinsights) module is a transport that will forward logs to [Azure Application Insights](https://docs.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview).
|
||||
|
||||
Given an application `foo` that logs via pino, you would use `pino-applicationinsights` like so:
|
||||
|
||||
``` sh
|
||||
$ node foo | pino-applicationinsights --key blablabla
|
||||
```
|
||||
|
||||
For full documentation of command line switches read [README](https://github.com/ovhemert/pino-applicationinsights#readme)
|
||||
|
||||
<a id="pino-azuretable"></a>
|
||||
### pino-azuretable
|
||||
The [pino-azuretable](https://www.npmjs.com/package/pino-azuretable) module is a transport that will forward logs to the [Azure Table Storage](https://azure.microsoft.com/en-us/services/storage/tables/).
|
||||
|
||||
Given an application `foo` that logs via pino, you would use `pino-azuretable` like so:
|
||||
|
||||
``` sh
|
||||
$ node foo | pino-azuretable --account storageaccount --key blablabla
|
||||
```
|
||||
|
||||
For full documentation of command line switches read [README](https://github.com/ovhemert/pino-azuretable#readme)
|
||||
|
||||
<a id="pino-cloudwatch"></a>
|
||||
### pino-cloudwatch
|
||||
|
||||
[pino-cloudwatch][pino-cloudwatch] is a transport that buffers and forwards logs to [Amazon CloudWatch][].
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-cloudwatch --group my-log-group
|
||||
```
|
||||
|
||||
[pino-cloudwatch]: https://github.com/dbhowell/pino-cloudwatch
|
||||
[Amazon CloudWatch]: https://aws.amazon.com/cloudwatch/
|
||||
|
||||
<a id="pino-couch"></a>
|
||||
### pino-couch
|
||||
|
||||
[pino-couch][pino-couch] uploads each log line as a [CouchDB][CouchDB] document.
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-couch -U https://couch-server -d mylogs
|
||||
```
|
||||
|
||||
[pino-couch]: https://github.com/IBM/pino-couch
|
||||
[CouchDB]: https://couchdb.apache.org
|
||||
|
||||
<a id="pino-datadog"></a>
|
||||
### pino-datadog
|
||||
The [pino-datadog](https://www.npmjs.com/package/pino-datadog) module is a transport that will forward logs to [DataDog](https://www.datadoghq.com/) through its API.
|
||||
|
||||
Given an application `foo` that logs via pino, you would use `pino-datadog` like so:
|
||||
|
||||
``` sh
|
||||
$ node foo | pino-datadog --key blablabla
|
||||
```
|
||||
|
||||
For full documentation of command line switches read [README](https://github.com/ovhemert/pino-datadog#readme)
|
||||
|
||||
<a id="pino-elasticsearch"></a>
|
||||
### pino-elasticsearch
|
||||
|
||||
[pino-elasticsearch][pino-elasticsearch] uploads the log lines in bulk
|
||||
to [Elasticsearch][elasticsearch], to be displayed in [Kibana][kibana].
|
||||
|
||||
It is extremely simple to use and setup
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-elasticsearch
|
||||
```
|
||||
|
||||
Assuming Elasticsearch is running on localhost.
|
||||
|
||||
To connect to an external Elasticsearch instance (recommended for production):
|
||||
|
||||
* Check that `network.host` is defined in the `elasticsearch.yml` configuration file. See [Elasticsearch Network Settings documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-network.html#common-network-settings) for more details.
|
||||
* Launch:
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-elasticsearch --node http://192.168.1.42:9200
|
||||
```
|
||||
|
||||
Assuming Elasticsearch is running on `192.168.1.42`.
|
||||
|
||||
To connect to AWS Elasticsearch:
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-elasticsearch --node https://es-url.us-east-1.es.amazonaws.com --es-version 6
|
||||
```
|
||||
|
||||
Then [create an index pattern](https://www.elastic.co/guide/en/kibana/current/setup.html) on `'pino'` (the default index key for `pino-elasticsearch`) on the Kibana instance.
|
||||
|
||||
[pino-elasticsearch]: https://github.com/pinojs/pino-elasticsearch
|
||||
[elasticsearch]: https://www.elastic.co/products/elasticsearch
|
||||
[kibana]: https://www.elastic.co/products/kibana
|
||||
|
||||
<a id="pino-gelf"></a>
|
||||
### pino-gelf
|
||||
|
||||
Pino GELF ([pino-gelf]) is a transport for the Pino logger. Pino GELF receives Pino logs from stdin and transforms them into [GELF format][gelf] before sending them to a remote [Graylog server][graylog] via UDP.
|
||||
|
||||
```sh
|
||||
$ node your-app.js | pino-gelf log
|
||||
```
|
||||
|
||||
[pino-gelf]: https://github.com/pinojs/pino-gelf
|
||||
[gelf]: https://docs.graylog.org/en/2.1/pages/gelf.html
|
||||
[graylog]: https://www.graylog.org/
|
||||
|
||||
<a id="pino-http-send"></a>
|
||||
### pino-http-send
|
||||
|
||||
[pino-http-send](https://npmjs.com/package/pino-http-send) is a configurable and low overhead
|
||||
transport that will batch logs and send to a specified URL.
|
||||
|
||||
```console
|
||||
$ node app.js | pino-http-send -u http://localhost:8080/logs
|
||||
```
|
||||
|
||||
<a id="pino-kafka"></a>
|
||||
### pino-kafka
|
||||
|
||||
[pino-kafka](https://github.com/ayZagen/pino-kafka) transport to send logs to [Apache Kafka](https://kafka.apache.org/).
|
||||
|
||||
```sh
|
||||
$ node index.js | pino-kafka -b 10.10.10.5:9200 -d mytopic
|
||||
```
|
||||
|
||||
<a id="pino-logdna"></a>
|
||||
### pino-logdna
|
||||
|
||||
[pino-logdna](https://github.com/logdna/pino-logdna) transport to send logs to [LogDNA](https://logdna.com).
|
||||
|
||||
```sh
|
||||
$ node index.js | pino-logdna --key YOUR_INGESTION_KEY
|
||||
```
|
||||
|
||||
Tags and other metadata can be included using the available command line options. See the [pino-logdna README](https://github.com/logdna/pino-logdna#options) for a full list.
|
||||
|
||||
<a id="pino-logflare"></a>
|
||||
### pino-logflare
|
||||
|
||||
[pino-logflare](https://github.com/Logflare/pino-logflare) transport to send logs to a [Logflare](https://logflare.app) `source`.
|
||||
|
||||
```sh
|
||||
$ node index.js | pino-logflare --key YOUR_KEY --source YOUR_SOURCE
|
||||
```
|
||||
|
||||
<a id="pino-mq"></a>
|
||||
### pino-mq
|
||||
|
||||
The `pino-mq` transport will take all messages received on `process.stdin` and send them over a message bus using JSON serialization.
|
||||
|
||||
This is useful for:
|
||||
|
||||
* moving backpressure from application to broker
|
||||
* transforming messages pressure to another component
|
||||
|
||||
```
|
||||
node app.js | pino-mq -u "amqp://guest:guest@localhost/" -q "pino-logs"
|
||||
```
|
||||
|
||||
Alternatively, a configuration file can be used:
|
||||
|
||||
```
|
||||
node app.js | pino-mq -c pino-mq.json
|
||||
```
|
||||
|
||||
A base configuration file can be initialized with:
|
||||
|
||||
```
|
||||
pino-mq -g
|
||||
```
|
||||
|
||||
For full documentation of command line switches and configuration see [the `pino-mq` README](https://github.com/itavy/pino-mq#readme)
|
||||
|
||||
<a id="pino-loki"></a>
|
||||
### pino-loki
|
||||
pino-loki is a transport that will forwards logs into [Grafana Loki](https://grafana.com/oss/loki/).
|
||||
Can be used in CLI version in a separate process or in a dedicated worker:
|
||||
|
||||
CLI :
|
||||
```console
|
||||
node app.js | pino-loki --hostname localhost:3100 --labels='{ "application": "my-application"}' --user my-username --password my-password
|
||||
```
|
||||
|
||||
Worker :
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino-loki',
|
||||
options: { host: 'localhost:3100' }
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
For full documentation and configuration, see the [README](https://github.com/Julien-R44/pino-loki).
|
||||
|
||||
<a id="pino-papertrail"></a>
|
||||
### pino-papertrail
|
||||
pino-papertrail is a transport that will forward logs to the [papertrail](https://papertrailapp.com) log service through an UDPv4 socket.
|
||||
|
||||
Given an application `foo` that logs via pino, and a papertrail destination that collects logs on port UDP `12345` on address `bar.papertrailapp.com`, you would use `pino-papertrail`
|
||||
like so:
|
||||
|
||||
```
|
||||
node yourapp.js | pino-papertrail --host bar.papertrailapp.com --port 12345 --appname foo
|
||||
```
|
||||
|
||||
|
||||
for full documentation of command line switches read [README](https://github.com/ovhemert/pino-papertrail#readme)
|
||||
|
||||
<a id="pino-pg"></a>
|
||||
### pino-pg
|
||||
[pino-pg](https://www.npmjs.com/package/pino-pg) stores logs into PostgreSQL.
|
||||
Full documentation in the [README](https://github.com/Xstoudi/pino-pg).
|
||||
|
||||
<a id="pino-mysql"></a>
|
||||
### pino-mysql
|
||||
|
||||
[pino-mysql][pino-mysql] loads pino logs into [MySQL][MySQL] and [MariaDB][MariaDB].
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-mysql -c db-configuration.json
|
||||
```
|
||||
|
||||
`pino-mysql` can extract and save log fields into corresponding database fields
|
||||
and/or save the entire log stream as a [JSON Data Type][JSONDT].
|
||||
|
||||
For full documentation and command line switches read the [README][pino-mysql].
|
||||
|
||||
[pino-mysql]: https://www.npmjs.com/package/pino-mysql
|
||||
[MySQL]: https://www.mysql.com/
|
||||
[MariaDB]: https://mariadb.org/
|
||||
[JSONDT]: https://dev.mysql.com/doc/refman/8.0/en/json.html
|
||||
|
||||
<a id="pino-redis"></a>
|
||||
### pino-redis
|
||||
|
||||
[pino-redis][pino-redis] loads pino logs into [Redis][Redis].
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-redis -U redis://username:password@localhost:6379
|
||||
```
|
||||
|
||||
[pino-redis]: https://github.com/buianhthang/pino-redis
|
||||
[Redis]: https://redis.io/
|
||||
|
||||
<a id="pino-sentry"></a>
|
||||
### pino-sentry
|
||||
|
||||
[pino-sentry][pino-sentry] loads pino logs into [Sentry][Sentry].
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-sentry --dsn=https://******@sentry.io/12345
|
||||
```
|
||||
|
||||
For full documentation of command line switches see the [pino-sentry README](https://github.com/aandrewww/pino-sentry/blob/master/README.md).
|
||||
|
||||
[pino-sentry]: https://www.npmjs.com/package/pino-sentry
|
||||
[Sentry]: https://sentry.io/
|
||||
|
||||
|
||||
<a id="pino-seq"></a>
|
||||
### pino-seq
|
||||
|
||||
[pino-seq][pino-seq] supports both out-of-process and in-process log forwarding to [Seq][Seq].
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-seq --serverUrl http://localhost:5341 --apiKey 1234567890 --property applicationName=MyNodeApp
|
||||
```
|
||||
|
||||
[pino-seq]: https://www.npmjs.com/package/pino-seq
|
||||
[Seq]: https://datalust.co/seq
|
||||
|
||||
<a id="pino-seq-transport"></a>
|
||||
### pino-seq-transport
|
||||
|
||||
[pino-seq-transport][pino-seq-transport] is a Pino v7+ compatible transport to forward log events to [Seq][Seq]
|
||||
from a dedicated worker:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: '@autotelic/pino-seq-transport',
|
||||
options: { serverUrl: 'http://localhost:5341' }
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
[pino-seq-transport]: https://github.com/autotelic/pino-seq-transport
|
||||
[Seq]: https://datalust.co/seq
|
||||
|
||||
<a id="pino-sentry-transport"></a>
|
||||
### pino-sentry-transport
|
||||
|
||||
[pino-sentry-transport][pino-sentry-transport] is a Pino v7+ compatible transport to forward log events to [Sentry][Sentry]
|
||||
from a dedicated worker:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino-sentry-transport',
|
||||
options: {
|
||||
sentry: {
|
||||
dsn: 'https://******@sentry.io/12345',
|
||||
}
|
||||
}
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
[pino-sentry-transport]: https://github.com/tomer-yechiel/pino-sentry-transport
|
||||
[Sentry]: https://sentry.io/
|
||||
|
||||
|
||||
<a id="pino-airbrake-transport"></a>
|
||||
### pino-airbrake-transport
|
||||
|
||||
[pino-airbrake-transport][pino-airbrake-transport] is a Pino v7+ compatible transport to forward log events to [Airbrake][Airbrake]
|
||||
from a dedicated worker:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino-airbrake-transport',
|
||||
options: {
|
||||
airbrake: {
|
||||
projectId: 1,
|
||||
projectKey: "REPLACE_ME",
|
||||
environment: "production",
|
||||
// additional options for airbrake
|
||||
performanceStats: false,
|
||||
},
|
||||
},
|
||||
level: "error", // minimum log level that should be sent to airbrake
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
[pino-airbrake-transport]: https://github.com/enricodeleo/pino-airbrake-transport
|
||||
[Airbrake]: https://airbrake.io/
|
||||
|
||||
<a id="pino-socket"></a>
|
||||
### pino-socket
|
||||
|
||||
[pino-socket][pino-socket] is a transport that will forward logs to an IPv4
|
||||
UDP or TCP socket.
|
||||
|
||||
As an example, use `socat` to fake a listener:
|
||||
|
||||
```sh
|
||||
$ socat -v udp4-recvfrom:6000,fork exec:'/bin/cat'
|
||||
```
|
||||
|
||||
Then run an application that uses `pino` for logging:
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-socket -p 6000
|
||||
```
|
||||
|
||||
Logs from the application should be observed on both consoles.
|
||||
|
||||
[pino-socket]: https://www.npmjs.com/package/pino-socket
|
||||
|
||||
<a id="pino-datadog-transport"></a>
|
||||
### pino-datadog-transport
|
||||
|
||||
[pino-datadog-transport][pino-datadog-transport] is a Pino v7+ compatible transport to forward log events to [Datadog][Datadog]
|
||||
from a dedicated worker:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino-datadog-transport',
|
||||
options: {
|
||||
ddClientConf: {
|
||||
authMethods: {
|
||||
apiKeyAuth: <your datadog API key>
|
||||
}
|
||||
},
|
||||
},
|
||||
level: "error", // minimum log level that should be sent to datadog
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
[pino-datadog-transport]: https://github.com/theogravity/pino-datadog-transport
|
||||
[Datadog]: https://www.datadoghq.com/
|
||||
|
||||
#### Logstash
|
||||
|
||||
The [pino-socket][pino-socket] module can also be used to upload logs to
|
||||
[Logstash][logstash] via:
|
||||
|
||||
```
|
||||
$ node app.js | pino-socket -a 127.0.0.1 -p 5000 -m tcp
|
||||
```
|
||||
|
||||
Assuming logstash is running on the same host and configured as
|
||||
follows:
|
||||
|
||||
```
|
||||
input {
|
||||
tcp {
|
||||
port => 5000
|
||||
}
|
||||
}
|
||||
|
||||
filter {
|
||||
json {
|
||||
source => "message"
|
||||
}
|
||||
}
|
||||
|
||||
output {
|
||||
elasticsearch {
|
||||
hosts => "127.0.0.1:9200"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See <https://www.elastic.co/guide/en/kibana/current/setup.html> to learn
|
||||
how to setup [Kibana][kibana].
|
||||
|
||||
For Docker users, see
|
||||
https://github.com/deviantony/docker-elk to setup an ELK stack.
|
||||
|
||||
<a id="pino-stackdriver"></a>
|
||||
### pino-stackdriver
|
||||
The [pino-stackdriver](https://www.npmjs.com/package/pino-stackdriver) module is a transport that will forward logs to the [Google Stackdriver](https://cloud.google.com/logging/) log service through its API.
|
||||
|
||||
Given an application `foo` that logs via pino, a stackdriver log project `bar`, and credentials in the file `/credentials.json`, you would use `pino-stackdriver`
|
||||
like so:
|
||||
|
||||
``` sh
|
||||
$ node foo | pino-stackdriver --project bar --credentials /credentials.json
|
||||
```
|
||||
|
||||
For full documentation of command line switches read [README](https://github.com/ovhemert/pino-stackdriver#readme)
|
||||
|
||||
<a id="pino-syslog"></a>
|
||||
### pino-syslog
|
||||
|
||||
[pino-syslog][pino-syslog] is a transforming transport that converts
|
||||
`pino` NDJSON logs to [RFC3164][rfc3164] compatible log messages. The `pino-syslog` module does not
|
||||
forward the logs anywhere, it merely re-writes the messages to `stdout`. But
|
||||
when used in combination with `pino-socket` the log messages can be relayed to a syslog server:
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-syslog | pino-socket -a syslog.example.com
|
||||
```
|
||||
|
||||
Example output for the "hello world" log:
|
||||
|
||||
```
|
||||
<134>Apr 1 16:44:58 MacBook-Pro-3 none[94473]: {"pid":94473,"hostname":"MacBook-Pro-3","level":30,"msg":"hello world","time":1459529098958}
|
||||
```
|
||||
|
||||
[pino-syslog]: https://www.npmjs.com/package/pino-syslog
|
||||
[rfc3164]: https://tools.ietf.org/html/rfc3164
|
||||
[logstash]: https://www.elastic.co/products/logstash
|
||||
|
||||
|
||||
<a id="pino-websocket"></a>
|
||||
### pino-websocket
|
||||
|
||||
[pino-websocket](https://www.npmjs.com/package/@abeai/pino-websocket) is a transport that will forward each log line to a websocket server.
|
||||
|
||||
```sh
|
||||
$ 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).
|
||||
|
||||
<a id="pino-slack-webhook"></a>
|
||||
### pino-slack-webhook
|
||||
|
||||
[pino-slack-webhook][pino-slack-webhook] is a Pino v7+ compatible transport to forward log events to [Slack][Slack]
|
||||
from a dedicated worker:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: '@youngkiu/pino-slack-webhook',
|
||||
options: {
|
||||
webhookUrl: 'https://hooks.slack.com/services/xxx/xxx/xxx',
|
||||
channel: '#pino-log',
|
||||
username: 'webhookbot',
|
||||
icon_emoji: ':ghost:'
|
||||
}
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
[pino-slack-webhook]: https://github.com/youngkiu/pino-slack-webhook
|
||||
[Slack]: https://slack.com/
|
||||
|
||||
[pino-pretty]: https://github.com/pinojs/pino-pretty
|
||||
|
||||
For full documentation of command line switches read the [README](https://github.com/abeai/pino-websocket#readme).
|
||||
|
||||
<a id="pino-axiom"></a>
|
||||
### pino-axiom
|
||||
|
||||
[pino-axiom](https://www.npmjs.com/package/pino-axiom) is a transport that will forward logs to [Axiom](https://axiom.co).
|
||||
|
||||
```javascript
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino-axiom',
|
||||
options: {
|
||||
orgId: 'YOUR-ORG-ID',
|
||||
token: 'YOUR-TOKEN',
|
||||
dataset: 'YOUR-DATASET',
|
||||
},
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
<a id="pino-opentelemetry-transport"></a>
|
||||
### pino-opentelemetry-transport
|
||||
|
||||
[pino-opentelemetry-transport](https://www.npmjs.com/package/pino-opentelemetry-transport) is a transport that will forward logs to an [OpenTelemetry log collector](https://opentelemetry.io/docs/collector/) using [OpenTelemetry JS instrumentation](https://opentelemetry.io/docs/instrumentation/js/).
|
||||
|
||||
```javascript
|
||||
const pino = require('pino')
|
||||
|
||||
const transport = pino.transport({
|
||||
target: 'pino-opentelemetry-transport',
|
||||
options: {
|
||||
resourceAttributes: {
|
||||
'service.name': 'test-service',
|
||||
'service.version': '1.0.0'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
Documentation on running a minimal example is available in the [README](https://github.com/Vunovati/pino-opentelemetry-transport#minimalistic-example).
|
||||
|
||||
<a id="@axiomhq/pino"></a>
|
||||
### @axiomhq/pino
|
||||
|
||||
[@axiomhq/pino](https://www.npmjs.com/package/@axiomhq/pino) is the official [Axiom](https://axiom.co/) transport for Pino, using [axiom-js](https://github.com/axiomhq/axiom-js).
|
||||
|
||||
```javascript
|
||||
import pino from 'pino';
|
||||
|
||||
const logger = pino(
|
||||
{ level: 'info' },
|
||||
pino.transport({
|
||||
target: '@axiomhq/pino',
|
||||
options: {
|
||||
dataset: process.env.AXIOM_DATASET,
|
||||
token: process.env.AXIOM_TOKEN,
|
||||
},
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
then you can use the logger as usual:
|
||||
|
||||
```js
|
||||
logger.info('Hello from pino!');
|
||||
```
|
||||
|
||||
For further examples, head over to the [examples](https://github.com/axiomhq/axiom-js/tree/main/examples/pino) directory.
|
||||
|
||||
<a id="pino-discord-webhook"></a>
|
||||
### pino-discord-webhook
|
||||
|
||||
[pino-discord-webhook](https://github.com/fabulousgk/pino-discord-webhook) is a Pino v7+ compatible transport to forward log events to a [Discord](http://discord.com) webhook from a dedicated worker.
|
||||
|
||||
```js
|
||||
import pino from 'pino'
|
||||
|
||||
const logger = pino({
|
||||
transport: {
|
||||
target: 'pino-discord-webhook',
|
||||
options: {
|
||||
webhookUrl: 'https://discord.com/api/webhooks/xxxx/xxxx',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
<a id="pino-logfmt"></a>
|
||||
### pino-logfmt
|
||||
|
||||
[pino-logfmt](https://github.com/botflux/pino-logfmt) is a Pino v7+ transport that formats logs into [logfmt](https://brandur.org/logfmt). This transport can output the formatted logs to stdout or file.
|
||||
|
||||
```js
|
||||
import pino from 'pino'
|
||||
|
||||
const logger = pino({
|
||||
transport: {
|
||||
target: 'pino-logfmt'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
<a id="communication-between-pino-and-transport"></a>
|
||||
## Communication between Pino and Transports
|
||||
Here we discuss some technical details of how Pino communicates with its [worker threads](https://nodejs.org/api/worker_threads.html).
|
||||
|
||||
Pino uses [`thread-stream`](https://github.com/pinojs/thread-stream) to create a stream for transports.
|
||||
When we create a stream with `thread-stream`, `thread-stream` spawns a [worker](https://github.com/pinojs/thread-stream/blob/f19ac8dbd602837d2851e17fbc7dfc5bbc51083f/index.js#L50-L60) (an independent JavaScript execution thread).
|
||||
|
||||
### Error messages
|
||||
How are error messages propagated from a transport worker to Pino?
|
||||
|
||||
Let's assume we have a transport with an error listener:
|
||||
```js
|
||||
// index.js
|
||||
const transport = pino.transport({
|
||||
target: './transport.js'
|
||||
})
|
||||
|
||||
transport.on('error', err => {
|
||||
console.error('error caught', err)
|
||||
})
|
||||
|
||||
const log = pino(transport)
|
||||
```
|
||||
|
||||
When our worker emits an error event, the worker has listeners for it: [error](https://github.com/pinojs/thread-stream/blob/f19ac8dbd602837d2851e17fbc7dfc5bbc51083f/lib/worker.js#L59-L70) and [unhandledRejection](https://github.com/pinojs/thread-stream/blob/f19ac8dbd602837d2851e17fbc7dfc5bbc51083f/lib/worker.js#L135-L141). These listeners send the error message to the main thread where Pino is present.
|
||||
|
||||
When Pino receives the error message, it further [emits](https://github.com/pinojs/thread-stream/blob/f19ac8dbd602837d2851e17fbc7dfc5bbc51083f/index.js#L349) the error message. Finally, the error message arrives at our `index.js` and is caught by our error listener.
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
# Web Frameworks
|
||||
|
||||
Since HTTP logging is a primary use case, Pino has first-class support for the Node.js
|
||||
web framework ecosystem.
|
||||
|
||||
- [Web Frameworks](#web-frameworks)
|
||||
- [Pino with Fastify](#pino-with-fastify)
|
||||
- [Pino with Express](#pino-with-express)
|
||||
- [Pino with Hapi](#pino-with-hapi)
|
||||
- [Pino with Restify](#pino-with-restify)
|
||||
- [Pino with Koa](#pino-with-koa)
|
||||
- [Pino with Node core `http`](#pino-with-node-core-http)
|
||||
- [Pino with Nest](#pino-with-nest)
|
||||
- [Pino with H3](#pino-with-h3)
|
||||
|
||||
<a id="fastify"></a>
|
||||
## Pino with Fastify
|
||||
|
||||
The Fastify web framework comes bundled with Pino by default, simply set Fastify's
|
||||
`logger` option to `true` and use `request.log` or `reply.log` for log messages that correspond
|
||||
to each request:
|
||||
|
||||
```js
|
||||
const fastify = require('fastify')({
|
||||
logger: true
|
||||
})
|
||||
|
||||
fastify.get('/', async (request, reply) => {
|
||||
request.log.info('something')
|
||||
return { hello: 'world' }
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, (err) => {
|
||||
if (err) {
|
||||
fastify.log.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
The `logger` option can also be set to an object, which will be passed through directly
|
||||
as the [`pino` options object](/docs/api.md#options-object).
|
||||
|
||||
See the [fastify documentation](https://www.fastify.io/docs/latest/Reference/Logging/) for more information.
|
||||
|
||||
<a id="express"></a>
|
||||
## Pino with Express
|
||||
|
||||
```sh
|
||||
npm install pino-http
|
||||
```
|
||||
|
||||
```js
|
||||
const app = require('express')()
|
||||
const pino = require('pino-http')()
|
||||
|
||||
app.use(pino)
|
||||
|
||||
app.get('/', function (req, res) {
|
||||
req.log.info('something')
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
app.listen(3000)
|
||||
```
|
||||
|
||||
See the [pino-http README](https://npm.im/pino-http) for more info.
|
||||
|
||||
<a id="hapi"></a>
|
||||
## Pino with Hapi
|
||||
|
||||
```sh
|
||||
npm install hapi-pino
|
||||
```
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const Hapi = require('@hapi/hapi')
|
||||
const Pino = require('hapi-pino');
|
||||
|
||||
async function start () {
|
||||
// Create a server with a host and port
|
||||
const server = Hapi.server({
|
||||
host: 'localhost',
|
||||
port: 3000
|
||||
})
|
||||
|
||||
// Add the route
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/',
|
||||
handler: async function (request, h) {
|
||||
// request.log is HAPI's standard way of logging
|
||||
request.log(['a', 'b'], 'Request into hello world')
|
||||
|
||||
// a pino instance can also be used, which will be faster
|
||||
request.logger.info('In handler %s', request.path)
|
||||
|
||||
return 'hello world'
|
||||
}
|
||||
})
|
||||
|
||||
await server.register(Pino)
|
||||
|
||||
// also as a decorated API
|
||||
server.logger.info('another way for accessing it')
|
||||
|
||||
// and through Hapi standard logging system
|
||||
server.log(['subsystem'], 'third way for accessing it')
|
||||
|
||||
await server.start()
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
start().catch((err) => {
|
||||
console.log(err)
|
||||
process.exit(1)
|
||||
})
|
||||
```
|
||||
|
||||
See the [hapi-pino README](https://npm.im/hapi-pino) for more info.
|
||||
|
||||
<a id="restify"></a>
|
||||
## Pino with Restify
|
||||
|
||||
```sh
|
||||
npm install restify-pino-logger
|
||||
```
|
||||
|
||||
```js
|
||||
const server = require('restify').createServer({name: 'server'})
|
||||
const pino = require('restify-pino-logger')()
|
||||
|
||||
server.use(pino)
|
||||
|
||||
server.get('/', function (req, res) {
|
||||
req.log.info('something')
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
server.listen(3000)
|
||||
```
|
||||
|
||||
See the [restify-pino-logger README](https://npm.im/restify-pino-logger) for more info.
|
||||
|
||||
<a id="koa"></a>
|
||||
## Pino with Koa
|
||||
|
||||
```sh
|
||||
npm install koa-pino-logger
|
||||
```
|
||||
|
||||
```js
|
||||
const Koa = require('koa')
|
||||
const app = new Koa()
|
||||
const pino = require('koa-pino-logger')()
|
||||
|
||||
app.use(pino)
|
||||
|
||||
app.use((ctx) => {
|
||||
ctx.log.info('something else')
|
||||
ctx.body = 'hello world'
|
||||
})
|
||||
|
||||
app.listen(3000)
|
||||
```
|
||||
|
||||
See the [koa-pino-logger README](https://github.com/pinojs/koa-pino-logger) for more info.
|
||||
|
||||
<a id="http"></a>
|
||||
## Pino with Node core `http`
|
||||
|
||||
```sh
|
||||
npm install pino-http
|
||||
```
|
||||
|
||||
```js
|
||||
const http = require('http')
|
||||
const server = http.createServer(handle)
|
||||
const logger = require('pino-http')()
|
||||
|
||||
function handle (req, res) {
|
||||
logger(req, res)
|
||||
req.log.info('something else')
|
||||
res.end('hello world')
|
||||
}
|
||||
|
||||
server.listen(3000)
|
||||
```
|
||||
|
||||
See the [pino-http README](https://npm.im/pino-http) for more info.
|
||||
|
||||
|
||||
<a id="nest"></a>
|
||||
## Pino with Nest
|
||||
|
||||
```sh
|
||||
npm install nestjs-pino
|
||||
```
|
||||
|
||||
```ts
|
||||
import { NestFactory } from '@nestjs/core'
|
||||
import { Controller, Get, Module } from '@nestjs/common'
|
||||
import { LoggerModule, Logger } from 'nestjs-pino'
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly logger: Logger) {}
|
||||
|
||||
@Get()
|
||||
getHello() {
|
||||
this.logger.log('something')
|
||||
return `Hello world`
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
controllers: [AppController],
|
||||
imports: [LoggerModule.forRoot()]
|
||||
})
|
||||
class MyModule {}
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(MyModule)
|
||||
await app.listen(3000)
|
||||
}
|
||||
bootstrap()
|
||||
```
|
||||
|
||||
See the [nestjs-pino README](https://npm.im/nestjs-pino) for more info.
|
||||
|
||||
|
||||
<a id="h3"></a>
|
||||
## Pino with H3
|
||||
|
||||
```sh
|
||||
npm install pino-http
|
||||
```
|
||||
|
||||
```js
|
||||
import { createServer } from 'http'
|
||||
import { createApp } from 'h3'
|
||||
import pino from 'pino-http'
|
||||
|
||||
const app = createApp()
|
||||
|
||||
app.use(pino())
|
||||
|
||||
app.use('/', (req) => {
|
||||
req.log.info('something')
|
||||
return 'hello world'
|
||||
})
|
||||
|
||||
createServer(app).listen(process.env.PORT || 3000)
|
||||
```
|
||||
|
||||
See the [pino-http README](https://npm.im/pino-http) for more info.
|
||||
Reference in New Issue
Block a user