Comparing version 8.8.0 to 8.16.2
187
browser.js
@@ -16,5 +16,38 @@ 'use strict' | ||
res: mock, | ||
err: asErrValue | ||
err: asErrValue, | ||
errWithCause: asErrValue | ||
} | ||
function levelToValue (level, logger) { | ||
return level === 'silent' | ||
? Infinity | ||
: logger.levels.values[level] | ||
} | ||
const baseLogFunctionSymbol = Symbol('pino.logFuncs') | ||
const hierarchySymbol = Symbol('pino.hierarchy') | ||
const logFallbackMap = { | ||
error: 'log', | ||
fatal: 'error', | ||
warn: 'error', | ||
info: 'log', | ||
debug: 'log', | ||
trace: 'log' | ||
} | ||
function appendChildLogger (parentLogger, childLogger) { | ||
const newEntry = { | ||
logger: childLogger, | ||
parent: parentLogger[hierarchySymbol] | ||
} | ||
childLogger[hierarchySymbol] = newEntry | ||
} | ||
function setupBaseLogFunctions (logger, levels, proto) { | ||
const logFunctions = {} | ||
levels.forEach(level => { | ||
logFunctions[level] = proto[level] ? proto[level] : (_console[level] || _console[logFallbackMap[level] || 'log'] || noop) | ||
}) | ||
logger[baseLogFunctionSymbol] = logFunctions | ||
} | ||
function shouldSerialize (serialize, serializers) { | ||
@@ -51,7 +84,9 @@ if (Array.isArray(serialize)) { | ||
const levels = ['error', 'fatal', 'warn', 'info', 'debug', 'trace'] | ||
const customLevels = Object.keys(opts.customLevels || {}) | ||
const levels = ['error', 'fatal', 'warn', 'info', 'debug', 'trace'].concat(customLevels) | ||
if (typeof proto === 'function') { | ||
proto.error = proto.fatal = proto.warn = | ||
proto.info = proto.debug = proto.trace = proto | ||
levels.forEach(function (level) { | ||
proto[level] = proto | ||
}) | ||
} | ||
@@ -63,2 +98,6 @@ if (opts.enabled === false || opts.browser.disabled) opts.level = 'silent' | ||
setupBaseLogFunctions(logger, levels, proto) | ||
// setup root hierarchy entry | ||
appendChildLogger({}, logger) | ||
Object.defineProperty(logger, 'levelVal', { | ||
@@ -79,3 +118,3 @@ get: getLevelVal | ||
} | ||
logger.levels = pino.levels | ||
logger.levels = getLevels(opts) | ||
logger.level = level | ||
@@ -98,5 +137,3 @@ | ||
function getLevelVal () { | ||
return this.level === 'silent' | ||
? Infinity | ||
: this.levels.values[this.level] | ||
return levelToValue(this.level, this) | ||
} | ||
@@ -113,8 +150,12 @@ | ||
set(setOpts, logger, 'error', 'log') // <-- must stay first | ||
set(setOpts, logger, 'fatal', 'error') | ||
set(setOpts, logger, 'warn', 'error') | ||
set(setOpts, logger, 'info', 'log') | ||
set(setOpts, logger, 'debug', 'log') | ||
set(setOpts, logger, 'trace', 'log') | ||
set(this, setOpts, logger, 'error') // <-- must stay first | ||
set(this, setOpts, logger, 'fatal') | ||
set(this, setOpts, logger, 'warn') | ||
set(this, setOpts, logger, 'info') | ||
set(this, setOpts, logger, 'debug') | ||
set(this, setOpts, logger, 'trace') | ||
customLevels.forEach((level) => { | ||
set(this, setOpts, logger, level) | ||
}) | ||
} | ||
@@ -141,8 +182,6 @@ | ||
this._childLevel = (parent._childLevel | 0) + 1 | ||
this.error = bind(parent, bindings, 'error') | ||
this.fatal = bind(parent, bindings, 'fatal') | ||
this.warn = bind(parent, bindings, 'warn') | ||
this.info = bind(parent, bindings, 'info') | ||
this.debug = bind(parent, bindings, 'debug') | ||
this.trace = bind(parent, bindings, 'trace') | ||
// make sure bindings are available in the `set` function | ||
this.bindings = bindings | ||
if (childSerializers) { | ||
@@ -159,3 +198,10 @@ this.serializers = childSerializers | ||
Child.prototype = this | ||
return new Child(this) | ||
const newLogger = new Child(this) | ||
// must happen before the level is assigned | ||
appendChildLogger(this, newLogger) | ||
// required to actually initialize the logger functions for any given child | ||
newLogger.level = this.level | ||
return newLogger | ||
} | ||
@@ -165,2 +211,22 @@ return logger | ||
function getLevels (opts) { | ||
const customLevels = opts.customLevels || {} | ||
const values = Object.assign({}, pino.levels.values, customLevels) | ||
const labels = Object.assign({}, pino.levels.labels, invertObject(customLevels)) | ||
return { | ||
values, | ||
labels | ||
} | ||
} | ||
function invertObject (obj) { | ||
const inverted = {} | ||
Object.keys(obj).forEach(function (key) { | ||
inverted[obj[key]] = key | ||
}) | ||
return inverted | ||
} | ||
pino.levels = { | ||
@@ -188,15 +254,50 @@ values: { | ||
function set (opts, logger, level, fallback) { | ||
const proto = Object.getPrototypeOf(logger) | ||
logger[level] = logger.levelVal > logger.levels.values[level] | ||
function getBindingChain (logger) { | ||
const bindings = [] | ||
if (logger.bindings) { | ||
bindings.push(logger.bindings) | ||
} | ||
// traverse up the tree to get all bindings | ||
let hierarchy = logger[hierarchySymbol] | ||
while (hierarchy.parent) { | ||
hierarchy = hierarchy.parent | ||
if (hierarchy.logger.bindings) { | ||
bindings.push(hierarchy.logger.bindings) | ||
} | ||
} | ||
return bindings.reverse() | ||
} | ||
function set (self, opts, rootLogger, level) { | ||
// override the current log functions with either `noop` or the base log function | ||
self[level] = levelToValue(self.level, rootLogger) > levelToValue(level, rootLogger) | ||
? noop | ||
: (proto[level] ? proto[level] : (_console[level] || _console[fallback] || noop)) | ||
: rootLogger[baseLogFunctionSymbol][level] | ||
wrap(opts, logger, level) | ||
if (!opts.transmit && self[level] === noop) { | ||
return | ||
} | ||
// make sure the log format is correct | ||
self[level] = createWrap(self, opts, rootLogger, level) | ||
// prepend bindings if it is not the root logger | ||
const bindings = getBindingChain(self) | ||
if (bindings.length === 0) { | ||
// early exit in case for rootLogger | ||
return | ||
} | ||
self[level] = prependBindingsInArguments(bindings, self[level]) | ||
} | ||
function wrap (opts, logger, level) { | ||
if (!opts.transmit && logger[level] === noop) return | ||
function prependBindingsInArguments (bindings, logFunc) { | ||
return function () { | ||
return logFunc.apply(this, [...bindings, ...arguments]) | ||
} | ||
} | ||
logger[level] = (function (write) { | ||
function createWrap (self, opts, rootLogger, level) { | ||
return (function (write) { | ||
return function LOG () { | ||
@@ -215,5 +316,5 @@ const ts = opts.timestamp() | ||
if (opts.transmit) { | ||
const transmitLevel = opts.transmit.level || logger.level | ||
const transmitValue = pino.levels.values[transmitLevel] | ||
const methodValue = pino.levels.values[level] | ||
const transmitLevel = opts.transmit.level || self._level | ||
const transmitValue = rootLogger.levels.values[transmitLevel] | ||
const methodValue = rootLogger.levels.values[level] | ||
if (methodValue < transmitValue) return | ||
@@ -225,9 +326,9 @@ transmit(this, { | ||
transmitLevel, | ||
transmitValue: pino.levels.values[opts.transmit.level || logger.level], | ||
transmitValue: rootLogger.levels.values[opts.transmit.level || self._level], | ||
send: opts.transmit.send, | ||
val: logger.levelVal | ||
val: levelToValue(self._level, rootLogger) | ||
}, args) | ||
} | ||
} | ||
})(logger[level]) | ||
})(self[baseLogFunctionSymbol][level]) | ||
} | ||
@@ -243,3 +344,3 @@ | ||
} | ||
o.level = pino.levels.values[level] | ||
o.level = logger.levels.values[level] | ||
let lvl = (logger._childLevel | 0) + 1 | ||
@@ -272,13 +373,2 @@ if (lvl < 1) lvl = 1 | ||
function bind (parent, bindings, level) { | ||
return function () { | ||
const args = new Array(1 + arguments.length) | ||
args[0] = bindings | ||
for (var i = 1; i < args.length; i++) { | ||
args[i] = arguments[i - 1] | ||
} | ||
return parent[level].apply(this, args) | ||
} | ||
} | ||
function transmit (logger, opts, args) { | ||
@@ -373,1 +463,4 @@ const send = opts.send | ||
/* eslint-enable */ | ||
module.exports.default = pino | ||
module.exports.pino = pino |
@@ -61,3 +61,3 @@ # API | ||
One of `'fatal'`, `'error'`, `'warn'`, `'info`', `'debug'`, `'trace'` or `'silent'`. | ||
One of `'fatal'`, `'error'`, `'warn'`, `'info'`, `'debug'`, `'trace'` or `'silent'`. | ||
@@ -125,2 +125,4 @@ Additional levels can be added to the instance via the `customLevels` option. | ||
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 | ||
@@ -176,10 +178,43 @@ logged JSON. | ||
}, 'Message 1') | ||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","appName":"My app","description":"Ok","level-label":"info","msg":"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","appName":"My app","description":"Ok","level-label":"error","msg":"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. | ||
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), | ||
@@ -347,4 +382,4 @@ the object returned from the `mixin` method will also be nested. Prior versions would mix | ||
Changes the shape of the bindings. The default shape is `{ pid, hostname }`. | ||
The function takes a single argument, the bindings object. It will | ||
be called every time a child logger is created. | ||
The function takes a single argument, the bindings object, which can be configured | ||
using the [`base` option](#opt-base). Called once when creating logger. | ||
@@ -391,2 +426,21 @@ ```js | ||
#### `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) | ||
@@ -850,2 +904,22 @@ | ||
##### `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) | ||
@@ -899,3 +973,3 @@ | ||
<a id="flush"></a> | ||
### `logger.flush()` | ||
### `logger.flush([cb])` | ||
@@ -905,3 +979,3 @@ Flushes the content of the buffer when using `pino.destination({ | ||
This is an asynchronous, fire and forget, operation. | ||
This is an asynchronous, best used as fire and forget, operation. | ||
@@ -915,2 +989,4 @@ The use case is primarily for asynchronous logging, which may buffer | ||
If there is a need to wait for the logs to be flushed, a callback should be used. | ||
* See [`destination` parameter](#destination) | ||
@@ -1174,2 +1250,3 @@ * See [Asynchronous Logging ⇗](/docs/asynchronous.md) | ||
* `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) | ||
@@ -1204,3 +1281,3 @@ <a id="pino-multistream"></a> | ||
In order for `multistream` to work, the log level __must__ be set to the lowest level used in the streams array. | ||
In order for `multistream` to work, the log level __must__ be set to the lowest level used in the streams array. Default is `info`. | ||
@@ -1219,3 +1296,3 @@ #### Options | ||
var streams = [ | ||
{stream: process.stdout}, | ||
{level: 'debug', stream: process.stdout}, | ||
{level: 'error', stream: process.stderr}, | ||
@@ -1222,0 +1299,0 @@ ] |
@@ -77,1 +77,2 @@ # Pino Ecosystem | ||
+ [`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 |
@@ -15,2 +15,3 @@ # Help | ||
* [Avoid Message Conflict](#avoid-message-conflict) | ||
* [Best performance for logging to `stdout`](#best-performance-for-stdout) | ||
@@ -292,1 +293,14 @@ <a id="rotate"></a> | ||
``` | ||
<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. |
@@ -8,67 +8,29 @@ # Pretty Printing | ||
```sh | ||
$ cat app.log | pino-pretty | ||
``` | ||
For almost all situations, this is the recommended way to prettify logs. The | ||
programmatic API, described in the next section, is primarily for integration | ||
purposes with other CLI-based prettifiers. | ||
## Prettifier API | ||
Pino prettifier modules are extra modules that provide a CLI for parsing NDJSON | ||
log lines piped via `stdin` and expose an API that conforms to the Pino | ||
[metadata streams](/docs/api.md#metadata) API. | ||
The API requires modules provide a factory function that returns a prettifier | ||
function. This prettifier function must accept either a string of NDJSON or | ||
a Pino log object. A pseudo-example of such a prettifier is: | ||
The uninitialized Pino instance is passed as `this` into prettifier factory function, | ||
so it can be accessed via closure by the returned prettifier function. | ||
```js | ||
module.exports = function myPrettifier (options) { | ||
// `this` is bound to the pino instance | ||
// Deal with whatever options are supplied. | ||
return function prettifier (inputData) { | ||
let logObject | ||
if (typeof inputData === 'string') { | ||
logObject = someJsonParser(inputData) | ||
} else if (isObject(inputData)) { | ||
logObject = inputData | ||
} | ||
if (!logObject) return inputData | ||
// implement prettification | ||
} | ||
function isObject (input) { | ||
return Object.prototype.toString.apply(input) === '[object Object]' | ||
} | ||
} | ||
``` | ||
The reference implementation of such a module is the [`pino-pretty`][pp] module. | ||
To learn more about creating a custom prettifier module, refer to the | ||
`pino-pretty` source code. | ||
Note: if the prettifier returns `undefined`, instead of a formatted line, nothing | ||
will be written to the destination stream. | ||
### API Example | ||
> #### NOTE: | ||
> For general usage, it is highly recommended that logs are piped into | ||
> the prettifier instead. Prettified logs are not easily parsed and cannot | ||
> be easily investigated at a later date. | ||
1. Install a prettifier module as a separate dependency, e.g. `npm install pino-pretty`. | ||
1. Instantiate the logger with the prettifier option: | ||
2. Instantiate the logger with the `transport.target` option set to `'pino-pretty'`: | ||
```js | ||
const pino = require('pino') | ||
const log = pino({ | ||
prettifier: require('pino-pretty') | ||
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 |
@@ -18,2 +18,4 @@ # Transports | ||
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). | ||
@@ -113,2 +115,15 @@ [worker-thread]: https://nodejs.org/dist/latest-v14.x/docs/api/worker_threads.html | ||
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]. | ||
@@ -140,3 +155,3 @@ | ||
for await (let obj of source) { | ||
const toDrain = !destination.write(obj.message.toUpperCase() + '\n') | ||
const toDrain = !destination.write(obj.msg.toUpperCase() + '\n') | ||
// This block will handle backpressure | ||
@@ -328,3 +343,3 @@ if (toDrain) { | ||
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' }, | ||
{ target: 'some-file-transport', options: { destination: '/dev/null' } | ||
{ target: 'some-file-transport', options: { destination: '/dev/null' } } | ||
] | ||
@@ -382,2 +397,3 @@ }) | ||
+ [@logtail/pino](#@logtail/pino) | ||
+ [pino-elasticsearch](#pino-elasticsearch) | ||
@@ -390,2 +406,5 @@ + [pino-pretty](#pino-pretty) | ||
+ [pino-datadog-transport](#pino-datadog-transport) | ||
+ [pino-slack-webhook](#pino-slack-webhook) | ||
+ [pino-axiom](#pino-axiom) | ||
+ [pino-opentelemetry-transport](#pino-opentelemetry-transport) | ||
@@ -418,3 +437,9 @@ ### Legacy | ||
<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> | ||
@@ -896,4 +921,70 @@ ### pino-applicationinsights | ||
<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="communication-between-pino-and-transport"></a> | ||
@@ -900,0 +991,0 @@ ## Communication between Pino and Transports |
@@ -27,2 +27,3 @@ # Web Frameworks | ||
}) | ||
fastify.get('/', async (request, reply) => { | ||
@@ -32,2 +33,9 @@ request.log.info('something') | ||
}) | ||
fastify.listen({ port: 3000 }, (err) => { | ||
if (err) { | ||
fastify.log.error(err) | ||
process.exit(1) | ||
} | ||
}) | ||
``` | ||
@@ -34,0 +42,0 @@ |
'use strict' | ||
module.exports = { version: '8.8.0' } | ||
module.exports = { version: '8.16.2' } |
@@ -6,5 +6,2 @@ 'use strict' | ||
const defaultLevels = Object.create(levels) | ||
defaultLevels.silent = Infinity | ||
const DEFAULT_INFO_LEVEL = levels.info | ||
@@ -17,5 +14,8 @@ | ||
let levels = defaultLevels | ||
const streamLevels = Object.create(levels) | ||
streamLevels.silent = Infinity | ||
if (opts.levels && typeof opts.levels === 'object') { | ||
levels = opts.levels | ||
Object.keys(opts.levels).forEach(i => { | ||
streamLevels[i] = opts.levels[i] | ||
}) | ||
} | ||
@@ -31,3 +31,4 @@ | ||
clone, | ||
[metadata]: true | ||
[metadata]: true, | ||
streamLevels | ||
} | ||
@@ -105,3 +106,3 @@ | ||
const { streams } = this | ||
const { streams, streamLevels } = this | ||
@@ -112,3 +113,3 @@ let level | ||
} else if (typeof dest.level === 'string') { | ||
level = levels[dest.level] | ||
level = streamLevels[dest.level] | ||
} else if (typeof dest.level === 'number') { | ||
@@ -115,0 +116,0 @@ level = dest.level |
@@ -23,2 +23,3 @@ 'use strict' | ||
errorKeySym, | ||
messageKeySym, | ||
useOnlyCustomLevelsSym, | ||
@@ -29,3 +30,4 @@ needsMetadataGsym, | ||
formatOptsSym, | ||
stringifiersSym | ||
stringifiersSym, | ||
msgPrefixSym | ||
} = require('./symbols') | ||
@@ -143,2 +145,6 @@ const { | ||
if (typeof options.msgPrefix === 'string') { | ||
instance[msgPrefixSym] = (this[msgPrefixSym] || '') + options.msgPrefix | ||
} | ||
instance[chindingsSym] = asChindings(instance, bindings) | ||
@@ -182,2 +188,3 @@ const childLevel = options.level || this.level | ||
const errorKey = this[errorKeySym] | ||
const messageKey = this[messageKeySym] | ||
const mixinMergeStrategy = this[mixinMergeStrategySym] || defaultMixinMergeStrategy | ||
@@ -195,3 +202,3 @@ let obj | ||
obj = _obj | ||
if (msg === undefined && _obj[errorKey]) { | ||
if (msg === undefined && _obj[messageKey] === undefined && _obj[errorKey]) { | ||
msg = _obj[errorKey].message | ||
@@ -202,3 +209,3 @@ } | ||
if (mixin) { | ||
obj = mixinMergeStrategy(obj, mixin(obj, num)) | ||
obj = mixinMergeStrategy(obj, mixin(obj, num, this)) | ||
} | ||
@@ -221,5 +228,12 @@ | ||
function flush () { | ||
function flush (cb) { | ||
if (cb != null && typeof cb !== 'function') { | ||
throw Error('callback must be a function') | ||
} | ||
const stream = this[streamSym] | ||
if ('flush' in stream) stream.flush(noop) | ||
if (typeof stream.flush === 'function') { | ||
stream.flush(cb || noop) | ||
} else if (cb) cb() | ||
} |
@@ -30,2 +30,3 @@ 'use strict' | ||
const mixinMergeStrategySym = Symbol('pino.mixinMergeStrategy') | ||
const msgPrefixSym = Symbol('pino.msgPrefix') | ||
@@ -70,3 +71,4 @@ const wildcardFirstSym = Symbol('pino.wildcardFirst') | ||
nestedKeyStrSym, | ||
mixinMergeStrategySym | ||
mixinMergeStrategySym, | ||
msgPrefixSym | ||
} |
@@ -24,3 +24,4 @@ 'use strict' | ||
errorKeySym, | ||
nestedKeyStrSym | ||
nestedKeyStrSym, | ||
msgPrefixSym | ||
} = require('./symbols') | ||
@@ -30,3 +31,4 @@ const { isMainThread } = require('worker_threads') | ||
function noop () {} | ||
function noop () { | ||
} | ||
@@ -57,5 +59,17 @@ function genLog (level, hook) { | ||
} | ||
// We do not use a coercive check for `msg` as it is | ||
// measurably slower than the explicit checks. | ||
if (typeof this[msgPrefixSym] === 'string' && msg !== undefined && msg !== null) { | ||
msg = this[msgPrefixSym] + msg | ||
} | ||
this[writeSym](o, format(msg, formatParams, this[formatOptsSym]), level) | ||
} else { | ||
this[writeSym](null, format(o === undefined ? n.shift() : o, n, this[formatOptsSym]), level) | ||
let msg = o === undefined ? n.shift() : o | ||
// We do not use a coercive check for `msg` as it is | ||
// measurably slower than the explicit checks. | ||
if (typeof this[msgPrefixSym] === 'string' && msg !== undefined && msg !== null) { | ||
msg = this[msgPrefixSym] + msg | ||
} | ||
this[writeSym](null, format(msg, n, this[formatOptsSym]), level) | ||
} | ||
@@ -148,3 +162,4 @@ } | ||
if (value === undefined) continue | ||
propStr += ',"' + key + '":' + value | ||
const strKey = asString(key) | ||
propStr += ',' + strKey + ':' + value | ||
} | ||
@@ -223,7 +238,11 @@ } | ||
const hasNodeCodeCoverage = process.env.NODE_V8_COVERAGE || process.env.V8_COVERAGE | ||
function buildSafeSonicBoom (opts) { | ||
const stream = new SonicBoom(opts) | ||
stream.on('error', filterBrokenPipe) | ||
// if we are sync: false, we must flush on exit | ||
if (!opts.sync && isMainThread) { | ||
// If we are sync: false, we must flush on exit | ||
// We must disable this if there is node code coverage due to | ||
// https://github.com/nodejs/node/issues/49344#issuecomment-1741776308. | ||
if (!hasNodeCodeCoverage && !opts.sync && isMainThread) { | ||
onExit.register(stream, autoEnd) | ||
@@ -230,0 +249,0 @@ |
@@ -5,3 +5,3 @@ 'use strict' | ||
const getCallers = require('./caller') | ||
const { join, isAbsolute } = require('path') | ||
const { join, isAbsolute, sep } = require('path') | ||
const sleep = require('atomic-sleep') | ||
@@ -75,3 +75,3 @@ const onExit = require('on-exit-leak-free') | ||
function transport (fullOptions) { | ||
const { pipeline, targets, levels, options = {}, worker = {}, caller = getCallers() } = fullOptions | ||
const { pipeline, targets, levels, dedupe, options = {}, worker = {}, caller = getCallers() } = fullOptions | ||
@@ -112,2 +112,6 @@ // Backwards compatibility | ||
if (dedupe) { | ||
options.dedupe = dedupe | ||
} | ||
return buildStream(fixTarget(target), options, worker) | ||
@@ -130,3 +134,7 @@ | ||
try { | ||
fixTarget = createRequire(filePath).resolve(origin) | ||
const context = filePath === 'node:repl' | ||
? process.cwd() + sep | ||
: filePath | ||
fixTarget = createRequire(context).resolve(origin) | ||
break | ||
@@ -133,0 +141,0 @@ } catch (err) { |
@@ -12,3 +12,3 @@ 'use strict' | ||
module.exports = async function ({ targets, levels }) { | ||
module.exports = async function ({ targets, levels, dedupe }) { | ||
targets = await Promise.all(targets.map(async (t) => { | ||
@@ -42,3 +42,3 @@ const fn = await loadTransportStreamBuilder(t.target) | ||
function process (stream) { | ||
const multi = pino.multistream(targets, { levels }) | ||
const multi = pino.multistream(targets, { levels, dedupe }) | ||
// TODO manage backpressure | ||
@@ -45,0 +45,0 @@ stream.on('data', function (chunk) { |
{ | ||
"name": "pino", | ||
"version": "8.8.0", | ||
"version": "8.16.2", | ||
"description": "super fast, all natural json logger", | ||
@@ -9,15 +9,2 @@ "main": "pino.js", | ||
"browser": "./browser.js", | ||
"files": [ | ||
"pino.js", | ||
"file.js", | ||
"pino.d.ts", | ||
"bin.js", | ||
"browser.js", | ||
"pretty.js", | ||
"usage.txt", | ||
"test", | ||
"docs", | ||
"example.js", | ||
"lib" | ||
], | ||
"scripts": { | ||
@@ -33,2 +20,6 @@ "docs": "docsify serve", | ||
"test-types": "tsc && tsd && ts-node test/types/pino.ts", | ||
"test:smoke": "smoker smoke:pino && smoker smoke:browser && smoker smoke:file", | ||
"smoke:pino": "node ./pino.js", | ||
"smoke:browser": "node ./browser.js", | ||
"smoke:file": "node ./file.js", | ||
"transpile": "node ./test/fixtures/ts/transpile.cjs", | ||
@@ -75,7 +66,7 @@ "cov-ui": "tap --ts --coverage-report=html", | ||
"@types/flush-write-stream": "^1.0.0", | ||
"@types/node": "^18.0.0", | ||
"@types/node": "^20.2.3", | ||
"@types/tap": "^15.0.6", | ||
"airtap": "4.0.4", | ||
"benchmark": "^2.1.4", | ||
"bole": "^4.0.0", | ||
"bole": "^5.0.5", | ||
"bunyan": "^1.8.14", | ||
@@ -87,3 +78,3 @@ "debug": "^4.3.4", | ||
"eslint-plugin-import": "^2.26.0", | ||
"eslint-plugin-n": "^15.2.2", | ||
"eslint-plugin-n": "15.7.0", | ||
"eslint-plugin-node": "^11.1.0", | ||
@@ -98,7 +89,8 @@ "eslint-plugin-promise": "^6.0.0", | ||
"loglevel": "^1.6.7", | ||
"pino-pretty": "^9.0.0", | ||
"midnight-smoker": "1.1.1", | ||
"pino-pretty": "^10.2.1", | ||
"pre-commit": "^1.2.2", | ||
"proxyquire": "^2.1.3", | ||
"pump": "^3.0.0", | ||
"rimraf": "^3.0.2", | ||
"rimraf": "^5.0.1", | ||
"semver": "^7.3.7", | ||
@@ -113,3 +105,3 @@ "split2": "^4.0.0", | ||
"tsd": "^0.24.1", | ||
"typescript": "^4.8.2", | ||
"typescript": "^5.1.3", | ||
"winston": "^3.7.2" | ||
@@ -121,3 +113,3 @@ }, | ||
"on-exit-leak-free": "^2.1.0", | ||
"pino-abstract-transport": "v1.0.0", | ||
"pino-abstract-transport": "v1.1.0", | ||
"pino-std-serializers": "^6.0.0", | ||
@@ -128,3 +120,3 @@ "process-warning": "^2.0.0", | ||
"safe-stable-stringify": "^2.3.1", | ||
"sonic-boom": "^3.1.0", | ||
"sonic-boom": "^3.7.0", | ||
"thread-stream": "^2.0.0" | ||
@@ -131,0 +123,0 @@ }, |
@@ -1,2 +0,1 @@ | ||
// Type definitions for pino 7.0 | ||
// Project: https://github.com/pinojs/pino.git, http://getpino.io | ||
@@ -43,3 +42,3 @@ // Definitions by: Peter Snider <https://github.com/psnider> | ||
interface redactOptions { | ||
export interface redactOptions { | ||
paths: string[]; | ||
@@ -50,3 +49,3 @@ censor?: string | ((value: any, path: string[]) => any); | ||
interface LoggerExtras<Options = LoggerOptions> extends EventEmitter { | ||
export interface LoggerExtras<Options = LoggerOptions> extends EventEmitter { | ||
/** | ||
@@ -86,3 +85,3 @@ * Exposes the Pino package version. Also available on the exported pino function. | ||
*/ | ||
child<ChildOptions extends pino.ChildLoggerOptions>(bindings: pino.Bindings, options?: ChildOptions): pino.Logger<Options & ChildOptions>; | ||
child<ChildOptions extends pino.ChildLoggerOptions = {}>(bindings: pino.Bindings, options?: ChildOptions): pino.Logger<Options & ChildOptions>; | ||
@@ -112,3 +111,3 @@ /** | ||
*/ | ||
isLevelEnabled(level: pino.LevelWithSilent | string): boolean; | ||
isLevelEnabled(level: pino.LevelWithSilentOrString): boolean; | ||
@@ -130,4 +129,5 @@ /** | ||
* Flushes the content of the buffer when using pino.destination({ sync: false }). | ||
* call the callback when finished | ||
*/ | ||
flush(): () => void; | ||
flush(cb?: (err?: Error) => void): void; | ||
} | ||
@@ -155,3 +155,3 @@ | ||
*/ | ||
level: pino.LevelWithSilent | string; | ||
level: pino.LevelWithSilentOrString; | ||
@@ -227,3 +227,5 @@ /** | ||
type Level = "fatal" | "error" | "warn" | "info" | "debug" | "trace"; | ||
type LevelOrString = Level | (string & {}); | ||
type LevelWithSilent = pino.Level | "silent"; | ||
type LevelWithSilentOrString = LevelWithSilent | (string & {}); | ||
@@ -234,5 +236,5 @@ type SerializerFn = (value: any) => any; | ||
type LevelChangeEventListener<Options = LoggerOptions> = ( | ||
lvl: LevelWithSilent | string, | ||
lvl: LevelWithSilentOrString, | ||
val: number, | ||
prevLvl: LevelWithSilent | string, | ||
prevLvl: LevelWithSilentOrString, | ||
prevVal: number, | ||
@@ -253,4 +255,4 @@ logger: Logger<Options> | ||
target: string | ||
options: TransportOptions | ||
level: LevelWithSilent | string | ||
options?: TransportOptions | ||
level?: LevelWithSilentOrString | ||
} | ||
@@ -274,2 +276,3 @@ | ||
levels?: Record<string, number> | ||
dedupe?: boolean | ||
} | ||
@@ -297,14 +300,14 @@ | ||
interface StreamEntry { | ||
interface StreamEntry<TLevel = Level> { | ||
stream: DestinationStream | ||
level?: Level | ||
level?: TLevel | ||
} | ||
interface MultiStreamRes { | ||
interface MultiStreamRes<TOriginLevel = Level> { | ||
write: (data: any) => void, | ||
add: (dest: StreamEntry | DestinationStream) => MultiStreamRes, | ||
add: <TLevel = Level>(dest: StreamEntry<TLevel> | DestinationStream) => MultiStreamRes<TOriginLevel & TLevel>, | ||
flushSync: () => void, | ||
minLevel: number, | ||
streams: StreamEntry[], | ||
clone(level: Level): MultiStreamRes, | ||
streams: StreamEntry<TOriginLevel>[], | ||
clone<TLevel = Level>(level: TLevel): MultiStreamRes<TLevel>, | ||
} | ||
@@ -358,3 +361,3 @@ | ||
*/ | ||
level?: LevelWithSilent | string; | ||
level?: LevelWithSilentOrString; | ||
@@ -418,6 +421,2 @@ /** | ||
/** | ||
* Allows to optionally define which prettifier module to use. | ||
*/ | ||
prettifier?: any; | ||
/** | ||
* Enables logging. Default: `true`. | ||
@@ -565,3 +564,3 @@ */ | ||
*/ | ||
level?: Level | string; | ||
level?: LevelOrString; | ||
/** | ||
@@ -574,2 +573,9 @@ * Remotely record log messages. | ||
}; | ||
/** | ||
* The disabled option will disable logging in browser if set to true, by default it is set to false. | ||
* | ||
* @example | ||
* const pino = require('pino')({browser: {disabled: true}}) | ||
*/ | ||
disabled?: boolean; | ||
}; | ||
@@ -611,2 +617,7 @@ /** | ||
/** | ||
* A string that would be prefixed to every message (and child message) | ||
*/ | ||
msgPrefix?: string | ||
/** | ||
* An object mapping to hook functions. Hook functions allow for customizing internal logger operations. | ||
@@ -639,6 +650,11 @@ * Hook functions must be synchronous functions. | ||
onChild?: OnChildCallback; | ||
/** | ||
* logs newline delimited JSON with `\r\n` instead of `\n`. Default: `false`. | ||
*/ | ||
crlf?: boolean; | ||
} | ||
interface ChildLoggerOptions { | ||
level?: Level | string; | ||
level?: LevelOrString; | ||
serializers?: { [key: string]: SerializerFn }; | ||
@@ -652,2 +668,3 @@ customLevels?: { [key: string]: number }; | ||
redact?: string[] | redactOptions; | ||
msgPrefix?: string | ||
} | ||
@@ -782,6 +799,6 @@ | ||
export function multistream( | ||
streamsArray: (DestinationStream | StreamEntry)[] | DestinationStream | StreamEntry, | ||
export function multistream<TLevel = Level>( | ||
streamsArray: (DestinationStream | StreamEntry<TLevel>)[] | DestinationStream | StreamEntry<TLevel>, | ||
opts?: MultiStreamOptions | ||
): MultiStreamRes | ||
): MultiStreamRes<TLevel> | ||
} | ||
@@ -822,3 +839,5 @@ | ||
export type Level = pino.Level; | ||
export type LevelOrString = pino.LevelOrString; | ||
export type LevelWithSilent = pino.LevelWithSilent; | ||
export type LevelWithSilentOrString = pino.LevelWithSilentOrString; | ||
export type LevelChangeEventListener = pino.LevelChangeEventListener; | ||
@@ -842,4 +861,4 @@ export type LogDescriptor = pino.LogDescriptor; | ||
export interface MultiStreamOptions extends pino.MultiStreamOptions {} | ||
export interface MultiStreamRes extends pino.MultiStreamRes {} | ||
export interface StreamEntry extends pino.StreamEntry {} | ||
export interface MultiStreamRes<TLevel = Level> extends pino.MultiStreamRes<TLevel> {} | ||
export interface StreamEntry<TLevel = Level> extends pino.StreamEntry<TLevel> {} | ||
export interface TransportBaseOptions extends pino.TransportBaseOptions {} | ||
@@ -846,0 +865,0 @@ export interface TransportMultiOptions extends pino.TransportMultiOptions {} |
10
pino.js
@@ -43,3 +43,4 @@ 'use strict' | ||
nestedKeyStrSym, | ||
mixinMergeStrategySym | ||
mixinMergeStrategySym, | ||
msgPrefixSym | ||
} = symbols | ||
@@ -107,3 +108,4 @@ const { epochTime, nullTime } = time | ||
edgeLimit, | ||
onChild | ||
onChild, | ||
msgPrefix | ||
} = opts | ||
@@ -155,2 +157,3 @@ | ||
if (mixin && typeof mixin !== 'function') throw Error(`Unknown mixin type "${typeof mixin}" - expected "function"`) | ||
if (msgPrefix && typeof msgPrefix !== 'string') throw Error(`Unknown msgPrefix type "${typeof msgPrefix}" - expected "string"`) | ||
@@ -183,3 +186,4 @@ assertDefaultLevelFound(level, customLevels, useOnlyCustomLevels) | ||
silent: noop, | ||
onChild | ||
onChild, | ||
[msgPrefixSym]: msgPrefix | ||
}) | ||
@@ -186,0 +190,0 @@ |
@@ -5,3 +5,3 @@ ![banner](pino-banner.png) | ||
[![npm version](https://img.shields.io/npm/v/pino)](https://www.npmjs.com/package/pino) | ||
[![Build Status](https://img.shields.io/github/workflow/status/pinojs/pino/CI)](https://github.com/pinojs/pino/actions) | ||
[![Build Status](https://img.shields.io/github/actions/workflow/status/pinojs/pino/ci.yml)](https://github.com/pinojs/pino/actions) | ||
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/) | ||
@@ -28,2 +28,3 @@ | ||
Using NPM: | ||
``` | ||
@@ -33,2 +34,7 @@ $ npm install pino | ||
Using YARN: | ||
``` | ||
$ yarn add pino | ||
``` | ||
If you would like to install pino v6, refer to https://github.com/pinojs/pino/tree/v6.x. | ||
@@ -107,3 +113,3 @@ | ||
<https://github.com/pinojs> | ||
<https://github.com/mcollina> | ||
@@ -110,0 +116,0 @@ <https://www.npmjs.com/~matteo.collina> |
@@ -423,2 +423,19 @@ 'use strict' | ||
test('correctly escape quote in a key', async ({ same }) => { | ||
const stream = sink() | ||
const instance = pino(stream) | ||
const obj = { 'some"obj': 'world' } | ||
instance.info(obj, 'a string') | ||
const result = await once(stream, 'data') | ||
delete result.time | ||
same(result, { | ||
level: 30, | ||
pid, | ||
hostname, | ||
msg: 'a string', | ||
'some"obj': 'world' | ||
}) | ||
same(Object.keys(obj), ['some"obj']) | ||
}) | ||
// https://github.com/pinojs/pino/issues/139 | ||
@@ -755,1 +772,72 @@ test('object and format string', async ({ same }) => { | ||
}) | ||
test('logger message should have the prefix message that defined in the logger creation', async ({ equal }) => { | ||
const stream = sink() | ||
const logger = pino({ | ||
msgPrefix: 'My name is Bond ' | ||
}, stream) | ||
logger.info('James Bond') | ||
const { msg } = await once(stream, 'data') | ||
equal(msg, 'My name is Bond James Bond') | ||
}) | ||
test('child message should have the prefix message that defined in the child creation', async ({ equal }) => { | ||
const stream = sink() | ||
const instance = pino(stream) | ||
const child = instance.child({}, { msgPrefix: 'My name is Bond ' }) | ||
child.info('James Bond') | ||
const { msg } = await once(stream, 'data') | ||
equal(msg, 'My name is Bond James Bond') | ||
}) | ||
test('child message should have the prefix message that defined in the child creation when logging with log meta', async ({ equal }) => { | ||
const stream = sink() | ||
const instance = pino(stream) | ||
const child = instance.child({}, { msgPrefix: 'My name is Bond ' }) | ||
child.info({ hello: 'world' }, 'James Bond') | ||
const { msg, hello } = await once(stream, 'data') | ||
equal(hello, 'world') | ||
equal(msg, 'My name is Bond James Bond') | ||
}) | ||
test('logged message should not have the prefix when not providing any message', async ({ equal }) => { | ||
const stream = sink() | ||
const instance = pino(stream) | ||
const child = instance.child({}, { msgPrefix: 'This should not be shown ' }) | ||
child.info({ hello: 'world' }) | ||
const { msg, hello } = await once(stream, 'data') | ||
equal(hello, 'world') | ||
equal(msg, undefined) | ||
}) | ||
test('child message should append parent prefix to current prefix that defined in the child creation', async ({ equal }) => { | ||
const stream = sink() | ||
const instance = pino({ | ||
msgPrefix: 'My name is Bond ' | ||
}, stream) | ||
const child = instance.child({}, { msgPrefix: 'James ' }) | ||
child.info('Bond') | ||
const { msg } = await once(stream, 'data') | ||
equal(msg, 'My name is Bond James Bond') | ||
}) | ||
test('child message should inherent parent prefix', async ({ equal }) => { | ||
const stream = sink() | ||
const instance = pino({ | ||
msgPrefix: 'My name is Bond ' | ||
}, stream) | ||
const child = instance.child({}) | ||
child.info('James Bond') | ||
const { msg } = await once(stream, 'data') | ||
equal(msg, 'My name is Bond James Bond') | ||
}) | ||
test('grandchild message should inherent parent prefix', async ({ equal }) => { | ||
const stream = sink() | ||
const instance = pino(stream) | ||
const child = instance.child({}, { msgPrefix: 'My name is Bond ' }) | ||
const grandchild = child.child({}) | ||
grandchild.info('James Bond') | ||
const { msg } = await once(stream, 'data') | ||
equal(msg, 'My name is Bond James Bond') | ||
}) |
@@ -14,2 +14,12 @@ 'use strict' | ||
if (process.env.CITGM) { | ||
// This looks like a some form of limitations of the CITGM test runner | ||
// or the HW/SW we run it on. This file can hang on Node.js v18.x. | ||
// The failure does not reproduce locally or on our CI. | ||
// Skipping it is the only way to keep pino in CITGM. | ||
// https://github.com/nodejs/citgm/pull/1002#issuecomment-1751942988 | ||
t.skip('Skipping on Node.js core CITGM because it hangs on v18.x') | ||
process.exit(0) | ||
} | ||
function test (file) { | ||
@@ -16,0 +26,0 @@ file = join('fixtures', 'broken-pipe', file) |
@@ -118,2 +118,25 @@ 'use strict' | ||
test('set custom level and use it', ({ end, same, is }) => { | ||
const expected = [ | ||
{ | ||
level: 31, | ||
msg: 'this is a custom level' | ||
} | ||
] | ||
const instance = pino({ | ||
customLevels: { | ||
success: 31 | ||
}, | ||
browser: { | ||
write (actual) { | ||
checkLogObjects(is, same, actual, expected.shift()) | ||
} | ||
} | ||
}) | ||
instance.success('this is a custom level') | ||
end() | ||
}) | ||
test('the wrong level throws', ({ end, throws }) => { | ||
@@ -120,0 +143,0 @@ const instance = pino() |
@@ -359,1 +359,41 @@ 'use strict' | ||
}) | ||
test('msg should take precedence over error message on mergingObject', async ({ same }) => { | ||
const err = new Error('myerror') | ||
const stream = sink() | ||
const instance = pino(stream) | ||
instance.error({ msg: 'my message', err }) | ||
const result = await once(stream, 'data') | ||
delete result.time | ||
same(result, { | ||
pid, | ||
hostname, | ||
level: 50, | ||
err: { | ||
type: 'Error', | ||
stack: err.stack, | ||
message: err.message | ||
}, | ||
msg: 'my message' | ||
}) | ||
}) | ||
test('considers messageKey when giving msg precedence over error', async ({ same }) => { | ||
const err = new Error('myerror') | ||
const stream = sink() | ||
const instance = pino({ messageKey: 'message' }, stream) | ||
instance.error({ message: 'my message', err }) | ||
const result = await once(stream, 'data') | ||
delete result.time | ||
same(result, { | ||
pid, | ||
hostname, | ||
level: 50, | ||
err: { | ||
type: 'Error', | ||
stack: err.stack, | ||
message: err.message | ||
}, | ||
message: 'my message' | ||
}) | ||
}) |
'use strict' | ||
const { Writable } = require('stream') | ||
const fs = require('fs') | ||
module.exports = (options) => { | ||
@@ -8,3 +9,4 @@ const myTransportStream = new Writable({ | ||
write (chunk, enc, cb) { | ||
console.log(chunk.toString()) | ||
// Bypass console.log() to avoid flakyness | ||
fs.writeSync(1, chunk.toString()) | ||
cb() | ||
@@ -11,0 +13,0 @@ } |
@@ -163,1 +163,57 @@ 'use strict' | ||
}) | ||
test('mixin receives logger as third parameter', async ({ ok, same }) => { | ||
const stream = sink() | ||
const instance = pino({ | ||
mixin (context, num, logger) { | ||
ok(logger !== null, 'logger should be defined') | ||
ok(logger !== undefined, 'logger should be defined') | ||
same(logger, instance) | ||
return { ...context, num } | ||
} | ||
}, stream) | ||
instance.level = name | ||
instance[name]({ | ||
message: '123' | ||
}, 'test') | ||
}) | ||
test('mixin receives child logger', async ({ ok, same }) => { | ||
const stream = sink() | ||
let child = null | ||
const instance = pino({ | ||
mixin (context, num, logger) { | ||
ok(logger !== null, 'logger should be defined') | ||
ok(logger !== undefined, 'logger should be defined') | ||
same(logger.expected, child.expected) | ||
return { ...context, num } | ||
} | ||
}, stream) | ||
instance.level = name | ||
instance.expected = false | ||
child = instance.child({}) | ||
child.expected = true | ||
child[name]({ | ||
message: '123' | ||
}, 'test') | ||
}) | ||
test('mixin receives logger even if child exists', async ({ ok, same }) => { | ||
const stream = sink() | ||
let child = null | ||
const instance = pino({ | ||
mixin (context, num, logger) { | ||
ok(logger !== null, 'logger should be defined') | ||
ok(logger !== undefined, 'logger should be defined') | ||
same(logger.expected, instance.expected) | ||
return { ...context, num } | ||
} | ||
}, stream) | ||
instance.level = name | ||
instance.expected = false | ||
child = instance.child({}) | ||
child.expected = true | ||
instance[name]({ | ||
message: '123' | ||
}, 'test') | ||
}) |
@@ -224,3 +224,3 @@ 'use strict' | ||
const nested = proxyquire('pino-pretty/lib/utils', { | ||
const safeBoom = proxyquire('pino-pretty/lib/utils/build-safe-sonic-boom.js', { | ||
'sonic-boom': function () { | ||
@@ -233,4 +233,7 @@ t.pass('sonic created') | ||
}) | ||
const nested = proxyquire('pino-pretty/lib/utils/index.js', { | ||
'./build-safe-sonic-boom.js': safeBoom | ||
}) | ||
const pretty = proxyquire('pino-pretty', { | ||
'./lib/utils': nested | ||
'./lib/utils/index.js': nested | ||
}) | ||
@@ -237,0 +240,0 @@ |
'use strict' | ||
const os = require('os') | ||
const { createWriteStream } = require('fs') | ||
const { | ||
createWriteStream | ||
} = require('fs') | ||
const { readFile } = require('fs').promises | ||
const { join } = require('path') | ||
@@ -9,5 +12,16 @@ const { test } = require('tap') | ||
const writer = require('flush-write-stream') | ||
const { once, getPathToNull } = require('./helper') | ||
const { | ||
once, | ||
getPathToNull, | ||
file, | ||
watchFileCreated | ||
} = require('./helper') | ||
const { promisify } = require('util') | ||
test('asynchronous logging', async ({ equal, teardown }) => { | ||
const sleep = promisify(setTimeout) | ||
test('asynchronous logging', async ({ | ||
equal, | ||
teardown | ||
}) => { | ||
const now = Date.now | ||
@@ -52,2 +66,4 @@ const hostname = os.hostname | ||
await once(child, 'close') | ||
// Wait for the last write to be flushed | ||
await sleep(100) | ||
equal(actual, expected) | ||
@@ -63,3 +79,6 @@ equal(actual2.trim(), expected2) | ||
test('sync false with child', async ({ equal, teardown }) => { | ||
test('sync false with child', async ({ | ||
equal, | ||
teardown | ||
}) => { | ||
const now = Date.now | ||
@@ -88,3 +107,5 @@ const hostname = os.hostname | ||
const dest = createWriteStream(getPathToNull()) | ||
dest.write = function (s) { actual += s } | ||
dest.write = function (s) { | ||
actual += s | ||
} | ||
const asyncLogger = pino(dest).child({ hello: 'world' }) | ||
@@ -119,5 +140,57 @@ | ||
test('flush does nothing with sync true (default)', async () => { | ||
test('flush does nothing with sync true (default)', async ({ equal }) => { | ||
const instance = require('..')() | ||
instance.flush() | ||
equal(instance.flush(), undefined) | ||
}) | ||
test('should still call flush callback even when does nothing with sync true (default)', (t) => { | ||
t.plan(3) | ||
const instance = require('..')() | ||
instance.flush((...args) => { | ||
t.ok('flush called') | ||
t.same(args, []) | ||
// next tick to make flush not called more than once | ||
process.nextTick(() => { | ||
t.ok('flush next tick called') | ||
}) | ||
}) | ||
}) | ||
test('should call the flush callback when flushed the data for async logger', async (t) => { | ||
const outputPath = file() | ||
async function getOutputLogLines () { | ||
return (await readFile(outputPath)).toString().trim().split('\n').map(JSON.parse) | ||
} | ||
const pino = require('../') | ||
const instance = pino({}, pino.destination({ | ||
dest: outputPath, | ||
// to make sure it does not flush on its own | ||
minLength: 4096 | ||
})) | ||
const flushPromise = promisify(instance.flush).bind(instance) | ||
instance.info('hello') | ||
await flushPromise() | ||
await watchFileCreated(outputPath) | ||
const [firstFlushData] = await getOutputLogLines() | ||
t.equal(firstFlushData.msg, 'hello') | ||
// should not flush this as no data accumulated that's bigger than min length | ||
instance.info('world') | ||
// Making sure data is not flushed yet | ||
const afterLogData = await getOutputLogLines() | ||
t.equal(afterLogData.length, 1) | ||
await flushPromise() | ||
// Making sure data is not flushed yet | ||
const afterSecondFlush = (await getOutputLogLines())[1] | ||
t.equal(afterSecondFlush.msg, 'world') | ||
}) |
@@ -16,3 +16,5 @@ 'use strict' | ||
test('eight million lines', async ({ equal, comment }) => { | ||
const skip = process.env.CI || process.env.CITGM | ||
test('eight million lines', { skip }, async ({ equal, comment }) => { | ||
const destination = file() | ||
@@ -19,0 +21,0 @@ await execa(process.argv[0], [join(__dirname, '..', 'fixtures', 'transport-many-lines.js'), destination]) |
@@ -204,2 +204,64 @@ 'use strict' | ||
test('pino.transport without specifying default levels', async ({ same, teardown }) => { | ||
const dest = file() | ||
const transport = pino.transport({ | ||
targets: [{ | ||
level: 'foo', | ||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), | ||
options: { destination: dest } | ||
}], | ||
levels: { foo: 25 } | ||
}) | ||
teardown(transport.end.bind(transport)) | ||
const instance = pino(transport) | ||
instance.info('hello') | ||
await Promise.all([watchFileCreated(dest)]) | ||
const result1 = JSON.parse(await readFile(dest)) | ||
delete result1.time | ||
same(result1, { | ||
pid, | ||
hostname, | ||
level: 30, | ||
msg: 'hello' | ||
}) | ||
}) | ||
test('pino.transport with two files and dedupe', async ({ same, teardown }) => { | ||
const dest1 = file() | ||
const dest2 = file() | ||
const transport = pino.transport({ | ||
dedupe: true, | ||
targets: [{ | ||
level: 'info', | ||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), | ||
options: { destination: dest1 } | ||
}, { | ||
level: 'error', | ||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), | ||
options: { destination: dest2 } | ||
}] | ||
}) | ||
teardown(transport.end.bind(transport)) | ||
const instance = pino(transport) | ||
instance.info('hello') | ||
instance.error('world') | ||
await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)]) | ||
const result1 = JSON.parse(await readFile(dest1)) | ||
delete result1.time | ||
same(result1, { | ||
pid, | ||
hostname, | ||
level: 30, | ||
msg: 'hello' | ||
}) | ||
const result2 = JSON.parse(await readFile(dest2)) | ||
delete result2.time | ||
same(result2, { | ||
pid, | ||
hostname, | ||
level: 50, | ||
msg: 'world' | ||
}) | ||
}) | ||
test('pino.transport with an array including a pino-pretty destination', async ({ same, match, teardown }) => { | ||
@@ -427,2 +489,3 @@ const dest1 = file() | ||
await once(child, 'close') | ||
await immediate() | ||
not(strip(actual).match(/Hello/), null) | ||
@@ -451,2 +514,3 @@ }) | ||
await once(child, 'close') | ||
await immediate() | ||
not(strip(actual).match(/Hello/), null) | ||
@@ -453,0 +517,0 @@ }) |
@@ -9,2 +9,3 @@ 'use strict' | ||
const { watchFileCreated, file } = require('../helper') | ||
const { promisify } = require('util') | ||
@@ -14,3 +15,3 @@ const { pid } = process | ||
test('thread-stream async flush', async ({ same }) => { | ||
test('thread-stream async flush', async ({ equal, same }) => { | ||
const destination = file() | ||
@@ -23,3 +24,5 @@ const transport = pino.transport({ | ||
instance.info('hello') | ||
instance.flush() | ||
equal(instance.flush(), undefined) | ||
await watchFileCreated(destination) | ||
@@ -35,1 +38,35 @@ const result = JSON.parse(await readFile(destination)) | ||
}) | ||
test('thread-stream async flush should call the passed callback', async (t) => { | ||
const outputPath = file() | ||
async function getOutputLogLines () { | ||
return (await readFile(outputPath)).toString().trim().split('\n').map(JSON.parse) | ||
} | ||
const transport = pino.transport({ | ||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'), | ||
options: { destination: outputPath } | ||
}) | ||
const instance = pino(transport) | ||
const flushPromise = promisify(instance.flush).bind(instance) | ||
instance.info('hello') | ||
await flushPromise() | ||
await watchFileCreated(outputPath) | ||
const [firstFlushData] = await getOutputLogLines() | ||
t.equal(firstFlushData.msg, 'hello') | ||
// should not flush this as no data accumulated that's bigger than min length | ||
instance.info('world') | ||
// Making sure data is not flushed yet | ||
const afterLogData = await getOutputLogLines() | ||
t.equal(afterLogData.length, 1) | ||
await flushPromise() | ||
// Making sure data is not flushed yet | ||
const afterSecondFlush = (await getOutputLogLines())[1] | ||
t.equal(afterSecondFlush.msg, 'world') | ||
}) |
@@ -5,4 +5,3 @@ import { expectType } from 'tsd' | ||
import pino from '../../pino' | ||
import { multistream } from "../../pino"; | ||
import pino, { multistream } from '../../pino' | ||
@@ -13,3 +12,3 @@ const streams = [ | ||
{ level: 'error' as const, stream: process.stderr }, | ||
{ level: 'fatal' as const, stream: createWriteStream('') } | ||
{ level: 'fatal' as const, stream: process.stderr }, | ||
] | ||
@@ -19,11 +18,14 @@ | ||
expectType<pino.MultiStreamRes>(pino.multistream([createWriteStream('')])) | ||
expectType<pino.MultiStreamRes>(pino.multistream({ level: 'error' as const, stream: process.stderr })) | ||
expectType<pino.MultiStreamRes>(pino.multistream([{ level: 'fatal' as const, stream: createWriteStream('') }])) | ||
expectType<pino.MultiStreamRes<'error'>>(pino.multistream({ level: 'error' as const, stream: process.stderr })) | ||
expectType<pino.MultiStreamRes<'fatal'>>(pino.multistream([{ level: 'fatal' as const, stream: createWriteStream('') }])) | ||
expectType<pino.MultiStreamRes>(pino.multistream(streams)) | ||
expectType<pino.MultiStreamRes>(pino.multistream(streams, {})) | ||
expectType<pino.MultiStreamRes>(pino.multistream(streams, { levels: { 'info': 30 } })) | ||
expectType<pino.MultiStreamRes>(pino.multistream(streams, { dedupe: true })) | ||
expectType<pino.MultiStreamRes>(pino.multistream(streams[0]).add(streams[1])) | ||
expectType<pino.MultiStreamRes<'error' | 'fatal'>>(pino.multistream(streams)) | ||
expectType<pino.MultiStreamRes<'error' | 'fatal'>>(pino.multistream(streams, {})) | ||
expectType<pino.MultiStreamRes<'error' | 'fatal'>>(pino.multistream(streams, { levels: { 'info': 30 } })) | ||
expectType<pino.MultiStreamRes<'error' | 'fatal'>>(pino.multistream(streams, { dedupe: true })) | ||
expectType<pino.MultiStreamRes<'error' | 'fatal'>>(pino.multistream(streams[0]).add(streams[1])) | ||
expectType<pino.MultiStreamRes<'error' | 'fatal'>>(multistream(streams)) | ||
expectType<pino.MultiStreamRes<'error'>>(multistream(streams).clone('error')) | ||
expectType<pino.MultiStreamRes>(multistream(process.stdout)); |
@@ -77,2 +77,19 @@ import { pino } from '../../pino' | ||
const transportsWithoutOptions = pino.transport({ | ||
targets: [ | ||
{ target: '#pino/pretty' }, | ||
{ target: '#pino/file' } | ||
], levels: { foo: 35 } | ||
}) | ||
pino(transports) | ||
expectType<pino.Logger>(pino({ | ||
transport: { | ||
targets: [ | ||
{ target: '#pino/pretty' }, | ||
{ target: '#pino/file' } | ||
], levels: { foo: 35 } | ||
}, | ||
})) | ||
const pipelineTransport = pino.transport({ | ||
@@ -124,1 +141,7 @@ pipeline: [{ | ||
}) | ||
// Dedupe | ||
pino.transport({ | ||
targets: [], | ||
dedupe: true, | ||
}) |
@@ -1,5 +0,5 @@ | ||
import { expectAssignable, expectType } from "tsd"; | ||
import { expectAssignable, expectType, expectNotAssignable } from "tsd"; | ||
import pino from "../../"; | ||
import type {LevelWithSilent, Logger, LogFn, P, DestinationStreamWithMetadata } from "../../pino"; | ||
import type {LevelWithSilent, Logger, LogFn, P, DestinationStreamWithMetadata, Level, LevelOrString, LevelWithSilentOrString, LoggerExtras } from "../../pino"; | ||
@@ -9,2 +9,3 @@ // NB: can also use `import * as pino`, but that form is callable as `pino()` | ||
const log = pino(); | ||
expectAssignable<LoggerExtras>(log); | ||
expectType<Logger>(log); | ||
@@ -16,5 +17,24 @@ expectType<LogFn>(log.info); | ||
const level: LevelWithSilent = 'silent'; | ||
expectAssignable<P.LevelWithSilent>(level); | ||
expectType<Parameters<typeof log.isLevelEnabled>>([log.level]); | ||
const level: Level = 'debug'; | ||
expectAssignable<string>(level); | ||
expectAssignable<P.Level>(level); | ||
const levelWithSilent: LevelWithSilent = 'silent'; | ||
expectAssignable<string>(levelWithSilent); | ||
expectAssignable<P.LevelWithSilent>(levelWithSilent); | ||
const levelOrString: LevelOrString = "myCustomLevel"; | ||
expectAssignable<string>(levelOrString); | ||
expectNotAssignable<pino.Level>(levelOrString); | ||
expectNotAssignable<pino.LevelWithSilent>(levelOrString); | ||
expectAssignable<pino.LevelWithSilentOrString>(levelOrString); | ||
const levelWithSilentOrString: LevelWithSilentOrString = "myCustomLevel"; | ||
expectAssignable<string>(levelWithSilentOrString); | ||
expectNotAssignable<pino.Level>(levelWithSilentOrString); | ||
expectNotAssignable<pino.LevelWithSilent>(levelWithSilentOrString); | ||
expectAssignable<pino.LevelOrString>(levelWithSilentOrString); | ||
function createStream(): DestinationStreamWithMetadata { | ||
@@ -21,0 +41,0 @@ return { write() {} }; |
@@ -114,2 +114,4 @@ import P, { pino } from "../../"; | ||
expectType<void>(log.flush()); | ||
log.flush((err?: Error) => undefined); | ||
log.child({ a: "property" }).info("hello child!"); | ||
@@ -329,1 +331,17 @@ log.level = "error"; | ||
pino({ | ||
crlf: true, | ||
}); | ||
const customLevels = { foo: 99 }; | ||
const customLevelLogger = pino({ customLevels }); | ||
type CustomLevelLogger = typeof customLevelLogger | ||
type CustomLevelLoggerLevels = pino.Level | keyof typeof customLevels | ||
const fn = (logger: Pick<CustomLevelLogger, CustomLevelLoggerLevels>) => {} | ||
const customLevelChildLogger = customLevelLogger.child({ name: "child" }) | ||
fn(customLevelChildLogger); // missing foo typing |
@@ -1,4 +0,5 @@ | ||
import { pino } from '../../pino' | ||
import { StreamEntry, pino } from '../../pino' | ||
import { join } from 'path' | ||
import { tmpdir } from 'os' | ||
import pinoPretty from 'pino-pretty' | ||
@@ -43,1 +44,36 @@ const destination = join( | ||
loggerMulti.info('test2') | ||
// custom levels | ||
const customLevels = { | ||
debug : 1, | ||
info : 2, | ||
network : 3, | ||
error : 4, | ||
}; | ||
type CustomLevels = keyof typeof customLevels; | ||
const pinoOpts = { | ||
level: 'debug', | ||
useOnlyCustomLevels: true, | ||
customLevels: customLevels, | ||
}; | ||
const multistreamOpts = { | ||
dedupe: true, | ||
levels: customLevels | ||
}; | ||
const streams: StreamEntry<CustomLevels>[] = [ | ||
{ level : 'debug', stream : pinoPretty() }, | ||
{ level : 'info', stream : pinoPretty() }, | ||
{ level : 'network', stream : pinoPretty() }, | ||
{ level : 'error', stream : pinoPretty() }, | ||
]; | ||
const loggerCustomLevel = pino(pinoOpts, pino.multistream(streams, multistreamOpts)); | ||
loggerCustomLevel.debug('test3') | ||
loggerCustomLevel.info('test4') | ||
loggerCustomLevel.error('test5') | ||
loggerCustomLevel.network('test6') |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 5 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
687030
190
13453
161
39
54
9
Updatedsonic-boom@^3.7.0