Comparing version 2.15.0 to 2.16.0
{ | ||
"name": "pino", | ||
"version": "2.15.0", | ||
"version": "2.16.0", | ||
"description": "super fast, all natural json logger", | ||
@@ -10,6 +10,24 @@ "main": "pino.js", | ||
}, | ||
"files": [ | ||
"pino.js", | ||
"browser.js", | ||
"pretty.js", | ||
"usage.txt", | ||
"test", | ||
"docs", | ||
"example.js" | ||
], | ||
"scripts": { | ||
"browser-test": "zuul tape test/browser.test.js --local", | ||
"test": "standard && tap --no-cov test/*test.js", | ||
"ci": "standard && tap --cov test/*test.js" | ||
"ci": "standard && tap --cov test/*test.js", | ||
"bench-all": "node benchmarks/runbench all", | ||
"bench-basic": "node benchmarks/runbench basic", | ||
"bench-object": "node benchmarks/runbench object", | ||
"bench-deepobject": "node benchmarks/runbench deepobject", | ||
"bench-multiarg": "node benchmarks/runbench multiarg", | ||
"bench-longstring": "node benchmarks/runbench longstring", | ||
"bench-child": "node benchmarks/runbench child", | ||
"bench-grandchild": "node benchmarks/runbench grandchild", | ||
"bench-conception": "node benchmarks/runbench conception" | ||
}, | ||
@@ -28,2 +46,6 @@ "precommit": "test", | ||
"author": "Matteo Collina <hello@matteocollina.com>", | ||
"contributors": [ | ||
"David Mark Clements <huperekchuno@googlemail.com>", | ||
"James Sumners <james.sumners@gmail.com>" | ||
], | ||
"license": "MIT", | ||
@@ -35,16 +57,19 @@ "bugs": { | ||
"devDependencies": { | ||
"benchmark": "^2.1.0", | ||
"bole": "^3.0.0", | ||
"bunyan": "^1.6.0", | ||
"debug": "^2.2.0", | ||
"benchmark": "^2.1.2", | ||
"bole": "^3.0.2", | ||
"bunyan": "^1.8.5", | ||
"debug": "^2.5.1", | ||
"fastbench": "^1.0.0", | ||
"flush-write-stream": "^1.0.0", | ||
"flush-write-stream": "^1.0.2", | ||
"fresh-require": "^1.0.3", | ||
"log": "^1.4.0", | ||
"loglevel": "^1.4.0", | ||
"pre-commit": "^1.1.2", | ||
"standard": "^8.0.0", | ||
"tap": "^7.0.0", | ||
"pre-commit": "^1.2.2", | ||
"pump": "^1.0.1", | ||
"standard": "^8.6.0", | ||
"steed": "^1.1.3", | ||
"tap": "^8.0.0", | ||
"tape": "^4.6.2", | ||
"winston": "^2.1.1", | ||
"through2": "^2.0.1", | ||
"winston": "^2.3.0", | ||
"zuul": "^3.11.1" | ||
@@ -54,11 +79,10 @@ }, | ||
"chalk": "^1.1.1", | ||
"core-util-is": "^1.0.2", | ||
"fast-json-parse": "^1.0.0", | ||
"fast-safe-stringify": "^1.1.0", | ||
"flatstr": "^1.0.0", | ||
"fast-safe-stringify": "^1.1.3", | ||
"flatstr": "^1.0.4", | ||
"object.assign": "^4.0.4", | ||
"once": "^1.3.3", | ||
"quick-format": "^2.0.4", | ||
"quick-format-unescaped": "^1.0.0", | ||
"split2": "^2.0.1" | ||
} | ||
} |
226
pino.js
'use strict' | ||
var stringifySafe = require('fast-safe-stringify') | ||
var format = require('quick-format') | ||
var format = require('quick-format-unescaped') | ||
var EventEmitter = require('events').EventEmitter | ||
var os = require('os') | ||
var fs = require('fs') | ||
var flatstr = require('flatstr') | ||
var once = require('once') | ||
var util = require('util') | ||
var pid = process.pid | ||
@@ -16,2 +18,13 @@ var hostname = os.hostname() | ||
var defaultOptions = { | ||
safe: true, | ||
name: undefined, | ||
serializers: {}, | ||
timestamp: true, | ||
slowtime: false, | ||
extreme: false, | ||
level: 'info', | ||
enabled: true | ||
} | ||
var levels = { | ||
@@ -26,8 +39,2 @@ fatal: 60, | ||
// private property | ||
Object.defineProperty(levels, 'silent', { | ||
value: 100, | ||
enumerable: false | ||
}) | ||
var nums = Object.keys(levels).reduce(function (o, k) { | ||
@@ -44,8 +51,2 @@ o[levels[k]] = k | ||
// private property | ||
Object.defineProperty(nums, '100', { | ||
value: 'silent', | ||
enumerable: false | ||
}) | ||
function streamIsBlockable (s) { | ||
@@ -58,28 +59,27 @@ if (s.hasOwnProperty('_handle') && s._handle.hasOwnProperty('fd') && s._handle.fd) return true | ||
function pino (opts, stream) { | ||
if (opts && (opts.writable || opts._writableState)) { | ||
stream = opts | ||
opts = null | ||
var iopts = opts | ||
var istream = stream | ||
if (iopts && (iopts.writable || iopts._writableState)) { | ||
istream = iopts | ||
iopts = defaultOptions | ||
} | ||
stream = stream || process.stdout | ||
opts = opts || {} | ||
var timestamp = (opts.hasOwnProperty('timestamp')) ? opts.timestamp : true | ||
var slowtime = opts.slowtime | ||
var safe = opts.safe !== false | ||
var stringify = safe ? stringifySafe : JSON.stringify | ||
var formatOpts = safe ? null : {lowres: true} | ||
var name = opts.name | ||
var level = opts.level || 'info' | ||
var serializers = opts.serializers || {} | ||
var end = ',"v":' + LOG_VERSION + '}\n' | ||
var cache = !opts.extreme ? null : { | ||
istream = istream || process.stdout | ||
iopts = extend({}, defaultOptions, iopts) | ||
// internal options | ||
iopts.stringify = iopts.safe ? stringifySafe : JSON.stringify | ||
iopts.formatOpts = {lowres: true} | ||
iopts.end = ',"v":' + LOG_VERSION + '}\n' | ||
iopts.cache = !iopts.extreme ? null : { | ||
size: 4096, | ||
buf: '' | ||
} | ||
iopts.chindings = '' | ||
if (opts.enabled === false) { | ||
level = 'silent' | ||
if (iopts.enabled === false) { | ||
iopts.level = 'silent' | ||
} | ||
var logger = new Pino(level, stream, serializers, stringify, end, name, timestamp, slowtime, '', cache, formatOpts) | ||
if (cache) { | ||
var logger = new Pino(iopts, istream) | ||
if (iopts.cache) { | ||
// setImmediate is causing a very weird crash: | ||
@@ -89,3 +89,3 @@ // Assertion failed: (cb_v->IsFunction()), function MakeCallback... | ||
setTimeout(function () { | ||
if (!streamIsBlockable(stream)) { | ||
if (!streamIsBlockable(istream)) { | ||
logger.emit('error', new Error('stream must have a file descriptor in extreme mode')) | ||
@@ -96,8 +96,9 @@ } | ||
onExit(function (code, evt) { | ||
if (cache.buf) { | ||
var buf = iopts.cache.buf | ||
if (buf) { | ||
// We need to block the process exit long enough to flush the buffer | ||
// to the destination stream. We do that by forcing a synchronous | ||
// write directly to the stream's file descriptor. | ||
var fd = (stream.fd) ? stream.fd : stream._handle.fd | ||
require('fs').writeSync(fd, cache.buf) | ||
var fd = (istream.fd) ? istream.fd : istream._handle.fd | ||
fs.writeSync(fd, buf) | ||
} | ||
@@ -124,22 +125,33 @@ if (!process._events[evt] || process._events[evt].length < 2 || !process._events[evt].filter(function (f) { | ||
}) | ||
Object.defineProperty(pino.levels.values, 'silent', {value: 100}) | ||
Object.defineProperty(pino.levels.labels, '100', {value: 'silent'}) | ||
function Pino (level, stream, serializers, stringify, end, name, timestamp, slowtime, chindings, cache, formatOpts) { | ||
pino.addLevel = function addLevel (name, lvl) { | ||
if (pino.levels.values.hasOwnProperty(name)) return false | ||
if (pino.levels.labels.hasOwnProperty(lvl)) return false | ||
pino.levels.values[name] = lvl | ||
pino.levels.labels[lvl] = name | ||
lscache[lvl] = flatstr('"level":' + Number(lvl)) | ||
Pino.prototype[name] = genLog(lvl) | ||
return true | ||
} | ||
function Pino (opts, stream) { | ||
this.stream = stream | ||
this.serializers = serializers | ||
this.stringify = stringify | ||
this.end = end | ||
this.name = name | ||
this.timestamp = timestamp | ||
this.slowtime = slowtime | ||
this.chindings = chindings | ||
this.cache = cache | ||
this.formatOpts = formatOpts | ||
this._setLevel(level) | ||
this.serializers = opts.serializers | ||
this.stringify = opts.stringify | ||
this.end = opts.end | ||
this.name = opts.name | ||
this.timestamp = opts.timestamp | ||
this.slowtime = opts.slowtime | ||
this.chindings = opts.chindings | ||
this.cache = opts.cache | ||
this.formatOpts = opts.formatOpts | ||
this._setLevel(opts.level) | ||
this._baseLog = flatstr(baseLog + | ||
(this.name === undefined ? '' : '"name":' + stringify(this.name) + ',')) | ||
(this.name === undefined ? '' : '"name":' + this.stringify(this.name) + ',')) | ||
if (timestamp === false) { | ||
if (opts.timestamp === false) { | ||
this.time = getNoTime | ||
} else if (slowtime) { | ||
} else if (opts.slowtime) { | ||
this.time = getSlowTime | ||
@@ -170,3 +182,3 @@ } else { | ||
if (this.emit) { | ||
this.emit('level-change', nums[num], num, nums[this._levelVal], this._levelVal) | ||
this.emit('level-change', pino.levels.labels[num], num, pino.levels.labels[this._levelVal], this._levelVal) | ||
} | ||
@@ -176,4 +188,4 @@ | ||
for (var key in levels) { | ||
if (num > levels[key]) { | ||
for (var key in pino.levels.values) { | ||
if (num > pino.levels.values[key]) { | ||
this[key] = noop | ||
@@ -188,12 +200,12 @@ continue | ||
Pino.prototype._setLevel = function _setLevel (level) { | ||
if (typeof level === 'number') { level = nums[level] } | ||
if (typeof level === 'number') { level = pino.levels.labels[level] } | ||
if (!levels[level]) { | ||
if (!pino.levels.values[level]) { | ||
throw new Error('unknown level ' + level) | ||
} | ||
this.levelVal = levels[level] | ||
this.levelVal = pino.levels.values[level] | ||
} | ||
Pino.prototype._getLevel = function _getLevel (level) { | ||
return nums[this.levelVal] | ||
return pino.levels.labels[this.levelVal] | ||
} | ||
@@ -212,28 +224,2 @@ | ||
// magically escape strings for json | ||
// relying on their charCodeAt | ||
// everything below 32 needs JSON.stringify() | ||
// 34 and 92 happens all the time, so we | ||
// have a fast case for them | ||
function escape (s) { | ||
var str = s.toString() | ||
var result = '' | ||
var last = 0 | ||
var l = str.length | ||
var point = 255 | ||
for (var i = 0; i < l && point >= 32; i++) { | ||
point = str.charCodeAt(i) | ||
if (point === 34 || point === 92) { | ||
result += str.slice(last, i) + '\\' + str[i] | ||
last = i + 1 | ||
} | ||
} | ||
if (last === 0) { | ||
result = str | ||
} else { | ||
result += str.slice(last) | ||
} | ||
return point < 32 ? JSON.stringify(str) : '"' + result + '"' | ||
} | ||
Pino.prototype.asJson = function asJson (obj, msg, num) { | ||
@@ -247,3 +233,3 @@ if (!msg && obj instanceof Error) { | ||
if (msg != undefined) { | ||
data += ',"msg":' + escape(msg) | ||
data += ',"msg":' + JSON.stringify(msg) | ||
} | ||
@@ -254,10 +240,9 @@ var value | ||
data += ',"type":"Error","stack":' + this.stringify(obj.stack) | ||
} else { | ||
for (var key in obj) { | ||
value = obj[key] | ||
if (obj.hasOwnProperty(key) && value !== undefined) { | ||
value = this.stringify(this.serializers[key] ? this.serializers[key](value) : value) | ||
if (value !== undefined) { | ||
data += ',"' + key + '":' + value | ||
} | ||
} | ||
for (var key in obj) { | ||
value = obj[key] | ||
if (obj.hasOwnProperty(key) && value !== undefined) { | ||
value = this.stringify(this.serializers[key] ? this.serializers[key](value) : value) | ||
if (value !== undefined) { | ||
data += ',"' + key + '":' + value | ||
} | ||
@@ -299,14 +284,16 @@ } | ||
return new Pino( | ||
bindings.level || this.level, | ||
this.stream, | ||
bindings.hasOwnProperty('serializers') ? extend(this.serializers, bindings.serializers) : this.serializers, | ||
this.stringify, | ||
this.end, | ||
this.name, | ||
this.timestamp, | ||
this.slowtime, | ||
data, | ||
this.cache, | ||
this.formatOpts) | ||
var opts = { | ||
level: bindings.level || this.level, | ||
serializers: bindings.hasOwnProperty('serializers') ? extend(this.serializers, bindings.serializers) : this.serializers, | ||
stringify: this.stringify, | ||
end: this.end, | ||
name: this.name, | ||
timestamp: this.timestamp, | ||
slowtime: this.slowtime, | ||
chindings: data, | ||
cache: this.cache, | ||
formatOpts: this.formatOpts | ||
} | ||
return new Pino(opts, this.stream) | ||
} | ||
@@ -367,3 +354,3 @@ | ||
function asErrValue (err) { | ||
return { | ||
var obj = { | ||
type: err.constructor.name, | ||
@@ -373,4 +360,23 @@ message: err.message, | ||
} | ||
for (var key in err) { | ||
if (obj[key] === undefined) { | ||
obj[key] = err[key] | ||
} | ||
} | ||
return obj | ||
} | ||
function countInterp (s, i) { | ||
var n = 0 | ||
var pos = 0 | ||
while (true) { | ||
pos = s.indexOf(i, pos) | ||
if (pos >= 0) { | ||
++n | ||
pos += 2 | ||
} else break | ||
} | ||
return n | ||
} | ||
function genLog (z) { | ||
@@ -398,3 +404,9 @@ return function LOG (a, b, c, d, e, f, g, h, i, j, k) { | ||
if (p > 1) { | ||
o = format(n, this.formatOpts) | ||
l = typeof a === 'string' ? countInterp(a, '%j') : 0 | ||
if (l) { | ||
n.length = l + countInterp(a, '%d') + countInterp(a, '%s') + 1 | ||
o = String(util.format.apply(null, n)) | ||
} else { | ||
o = format(n, this.formatOpts) | ||
} | ||
} else if (p) { | ||
@@ -401,0 +413,0 @@ o = n[0] |
@@ -50,3 +50,6 @@ #! /usr/bin/env node | ||
function isPinoLine (line) { | ||
return line.hasOwnProperty('hostname') && line.hasOwnProperty('pid') && (line.hasOwnProperty('v') && line.v === 1) | ||
return line && | ||
line.hasOwnProperty('hostname') && | ||
line.hasOwnProperty('pid') && | ||
(line.hasOwnProperty('v') && line.v === 1) | ||
} | ||
@@ -53,0 +56,0 @@ |
835
README.md
@@ -13,14 +13,10 @@ ![banner](pino-banner.png) | ||
* [Benchmarks](#benchmarks) | ||
* [API](#api) | ||
* [Extreme mode explained](#extreme) | ||
* [How to use Pino with Express](#express) | ||
* [How to use Pino with Hapi](#hapi) | ||
* [How to use Pino with Restify](#restify) | ||
* [How to use Pino with Koa](#koa) | ||
* [How do I rotate log files?](#rotate) | ||
* [How do I redact sensitive information?](#redact) | ||
* [How to use Transports with Pino](#transports) | ||
* [API](docs/API.md) | ||
* [Extreme mode explained](docs/extreme.md) | ||
* [Pino Howtos](docs/howtos.md) | ||
* [Transports with Pino](docs/transports.md) | ||
* [Pino in the browser](#browser) | ||
* [Caveats](#caveats) | ||
* [Team](#team) | ||
* [Contributing](#contributing) | ||
* [Acknowledgements](#acknowledgements) | ||
@@ -79,3 +75,2 @@ * [License](#license) | ||
## Benchmarks | ||
As far as we know, it is one of the fastest loggers in town: | ||
@@ -141,814 +136,9 @@ | ||
<a name="cli"></a> | ||
## CLI | ||
To use the command line tool, we can install `pino` globally: | ||
```sh | ||
npm install -g pino | ||
``` | ||
Then we simply pipe a log file through `pino`: | ||
```sh | ||
cat log | pino | ||
``` | ||
There are also two transformer flags.. | ||
`-t` that converts Epoch timestamps to ISO timestamps. | ||
```sh | ||
cat log | pino -t | ||
``` | ||
and `-l` that flips the time and level on the standard output. | ||
```sh | ||
cat log | pino -l | ||
``` | ||
`pino -t` will transform this: | ||
```js | ||
{"pid":14139,"hostname":"MacBook-Pro-3.home","level":30,"msg":"hello world","time":1457537229339,"v":0} | ||
``` | ||
Into this: | ||
```js | ||
{"pid":14139,"hostname":"MacBook-Pro-3.home","level":30,"msg":"hello world","time":"2016-03-09T15:27:09.339Z","v":0} | ||
``` | ||
`pino -l` will transform this: | ||
```sh | ||
[2016-03-09T15:27:09.339Z] INFO (14139 on MacBook-Pro-3.home): hello world | ||
``` | ||
Into this: | ||
```sh | ||
INFO [2016-03-09T15:27:09.339Z] (14139 on MacBook-Pro-3.home): hello world | ||
``` | ||
<a name="api"></a> | ||
## API | ||
* <a href="#constructor"><code><b>pino()</b></code></a> | ||
* <a href="#child"><code>logger.<b>child()</b></code></a> | ||
* <a href="#level"><code>logger.<b>level</b></code></a> | ||
* <a href="#fatal"><code>logger.<b>fatal()</b></code></a> | ||
* <a href="#error"><code>logger.<b>error()</b></code></a> | ||
* <a href="#warn"><code>logger.<b>warn()</b></code></a> | ||
* <a href="#info"><code>logger.<b>info()</b></code></a> | ||
* <a href="#debug"><code>logger.<b>debug()</b></code></a> | ||
* <a href="#trace"><code>logger.<b>trace()</b></code></a> | ||
* <a href="#flush"><code>logger.<b>flush()</b></code></a> | ||
* <a href="#levelVal"><code>logger.<b>levelVal</b></code></a> | ||
* <a href="#level-change"><code>logger.on(<b>'level-change'</b>, fn)</code></a> | ||
* <a href="#levelValues"><code>logger.levels.<b>values</b></code> & <code>pino.levels.<b>values</b></code></a> | ||
* <a href="#levelLabels"><code>logger.levels.<b>labels</b></code> & <code>pino.levels.<b>labels</b></code></a> | ||
* <a href="#log_version"><code>pino.<b>LOG_VERSION</b></code> & <code>logger.<b>LOG_VERSION</b></code></a> | ||
* <a href="#reqSerializer"><code>pino.stdSerializers.<b>req</b></code></a> | ||
* <a href="#resSerializer"><code>pino.stdSerializers.<b>res</b></code></a> | ||
* <a href="#errSerializer"><code>pino.stdSerializers.<b>err</b></code></a> | ||
* <a href="#pretty"><code>pino.<b>pretty()</b></code></a> | ||
<a name="constructor"></a> | ||
### pino([opts], [stream]) | ||
Returns a new logger. Allowed options are: | ||
* `safe`: avoid error causes by circular references in the object tree, | ||
default `true` | ||
* `name`: the name of the logger, default `undefined` | ||
* `serializers`: an object containing functions for custom serialization of objects. These functions should return an JSONifiable object and they should never throw | ||
* `timestamp`: Enables or disables the inclusion of a timestamp in the log message. `slowtime` has no effect if this option is set to `false`. Defaults to `true`. | ||
* `slowtime`: Outputs ISO time stamps (`'2016-03-09T15:18:53.889Z'`) instead of Epoch time stamps (`1457536759176`). **WARNING**: This option carries a 25% performance drop, we recommend using default Epoch timestamps and transforming logs after if required. The `pino -t` command will do this for you (see [CLI](#cli)). default `false`. | ||
* `extreme`: Enables extreme mode, yields an additional 60% performance (from 250ms down to 100ms per 10000 ops). There are trade-off's should be understood before usage. See [Extreme mode explained](#extreme). default `false` | ||
* `level`: one of `'fatal'`, `'error'`, `'warn'`, `'info`', `'debug'`, `'trace'`; also `'silent'` is supported to disable logging. | ||
* `enabled`: enables logging, defaults to `true`. | ||
`stream` is a Writable stream, defaults to `process.stdout`. | ||
Example: | ||
```js | ||
'use strict' | ||
var pino = require('pino') | ||
var logger = pino({ | ||
name: 'myapp', | ||
safe: true, | ||
serializers: { | ||
req: pino.stdSerializers.req, | ||
res: pino.stdSerializers.res | ||
} | ||
}) | ||
``` | ||
<a name="child"></a> | ||
### logger.child(bindings) | ||
Creates a child logger, setting all key-value pairs in `bindings` as | ||
properties in the log lines. All serializers will be applied to the | ||
given pair. | ||
Example: | ||
```js | ||
logger.child({ a: 'property' }).info('hello child!') | ||
// generates | ||
// {"pid":46497,"hostname":"MacBook-Pro-di-Matteo.local","level":30,"msg":"hello child!","time":1458124707120,"v":0,"a":"property"} | ||
``` | ||
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. | ||
From v2.x.x the log level of a child is mutable (whereas in | ||
v1.x.x it was immutable), and can be set independently of the parent. | ||
If a `level` property is present in the object passed to `child` it will | ||
override the child logger level. | ||
For example | ||
``` | ||
var logger = pino() | ||
logger.level = 'error' | ||
logger.info('nope') //does not log | ||
var child = logger.child({foo: 'bar'}) | ||
child.info('nope again') //does not log | ||
child.level = 'info' | ||
child.info('hooray') //will log | ||
logger.info('nope nope nope') //will not log, level is still set to error | ||
logger.child({ foo: 'bar', level: 'debug' }).debug('debug!') | ||
``` | ||
Child loggers inherit the serializers from the parent logger but it is | ||
possible to override them. | ||
For example | ||
``` | ||
var pino = require('./pino') | ||
var customSerializers = { | ||
test: function () { | ||
return 'this is my serializer' | ||
} | ||
} | ||
var child = pino().child({serializers: customSerializers}) | ||
child.info({test: 'should not show up'}) | ||
``` | ||
Will produce the following output: | ||
``` | ||
{"pid":7971,"hostname":"mycomputer.local","level":30,"time":1469488147985,"test":"this is my serializer","v":1} | ||
``` | ||
Also from version 2.x.x we can spawn child loggers from child loggers, for instance | ||
``` | ||
var logger = pino() | ||
var child = logger.child({father: true}) | ||
var childChild = child.child({baby: true}) | ||
``` | ||
Child logger creation is fast: | ||
``` | ||
benchBunyanCreation*10000: 1291.332ms | ||
benchBoleCreation*10000: 1630.542ms | ||
benchPinoCreation*10000: 352.330ms | ||
benchPinoExtremeCreation*10000: 102.282ms | ||
``` | ||
Logging through a child logger has little performance penalty: | ||
``` | ||
benchBunyanChild*10000: 1343.933ms | ||
benchBoleChild*10000: 1605.969ms | ||
benchPinoChild*10000: 334.573ms | ||
benchPinoExtremeChild*10000: 152.792ms | ||
``` | ||
Spawning children from children has negligible overhead: | ||
``` | ||
benchBunyanChildChild*10000: 1397.202ms | ||
benchPinoChildChild*10000: 338.930ms | ||
benchPinoExtremeChildChild*10000: 150.143ms | ||
``` | ||
<a name="level"></a> | ||
### logger.level | ||
Set this property to the desired logging level. | ||
In order of priority, available levels are: | ||
1. <a href="#fatal">`'fatal'`</a> | ||
2. <a href="#error">`'error'`</a> | ||
3. <a href="#warn">`'warn'`</a> | ||
4. <a href="#info">`'info'`</a> | ||
5. <a href="#debug">`'debug'`</a> | ||
6. <a href="#trace">`'trace'`</a> | ||
Example: `logger.level = 'info'` | ||
The logging level is a *minimum* level. For instance if `logger.level` is `'info'` then all `'fatal'`, `'error'`, `'warn'`, and `'info'` logs will be enabled. | ||
You can pass `'silent'` to disable logging. | ||
<a name="fatal"></a> | ||
### logger.fatal([obj], msg, [...]) | ||
Log at `'fatal'` level the given `msg`. If the first argument is an | ||
object, all its properties will be included in the JSON line. | ||
If more args follows `msg`, these will be used to format `msg` using | ||
[`util.format`](https://nodejs.org/api/util.html#util_util_format_format) | ||
<a name="error"></a> | ||
### logger.error([obj], msg, [...]) | ||
Log at `'error'` level the given `msg`. If the first argument is an | ||
object, all its properties will be included in the JSON line. | ||
If more args follows `msg`, these will be used to format `msg` using | ||
[`util.format`](https://nodejs.org/api/util.html#util_util_format_format) | ||
<a name="warn"></a> | ||
### logger.warn([obj], msg, [...]) | ||
Log at `'warn'` level the given `msg`. If the first argument is an | ||
object, all its properties will be included in the JSON line. | ||
If more args follows `msg`, these will be used to format `msg` using | ||
[`util.format`](https://nodejs.org/api/util.html#util_util_format_format) | ||
<a name="info"></a> | ||
### logger.info([obj], msg, [...]) | ||
Log at `'info'` level the given `msg`. If the first argument is an | ||
object, all its properties will be included in the JSON line. | ||
If more args follows `msg`, these will be used to format `msg` using | ||
[`util.format`](https://nodejs.org/api/util.html#util_util_format_format) | ||
<a name="debug"></a> | ||
### logger.debug([obj], msg, [...]) | ||
Log at `'debug'` level the given `msg`. If the first argument is an | ||
object, all its properties will be included in the JSON line. | ||
If more args follows `msg`, these will be used to format `msg` using | ||
[`util.format`](https://nodejs.org/api/util.html#util_util_format_format) | ||
<a name="trace"></a> | ||
### logger.trace([obj], msg, [...]) | ||
Log at `'trace'` level the given `msg`. If the first argument is an | ||
object, all its properties will be included in the JSON line. | ||
If more args follows `msg`, these will be used to format `msg` using | ||
[`util.format`](https://nodejs.org/api/util.html#util_util_format_format) | ||
<a name="flush"></a> | ||
### logger.flush() | ||
Flushes the content of the buffer in [extreme mode](#extreme). It has no effect if | ||
extreme mode is not enabled. | ||
<a name="levelVal"></a> | ||
### logger.levelVal | ||
Returns the integer value for the logger instance's logging level. | ||
<a name="level-change"></a> | ||
### logger.on('level-change', fn) | ||
Registers a listener function that is triggered when the level is changed. | ||
The listener is passed four arguments: `levelLabel`, `levelValue`, `previousLevelLabel`, `previousLevelValue`. | ||
**Note:** When browserified, this functionality will only be available if the `events` module has been required else where (e.g. if you're using streams in the browser). This allows for a trade-off between bundle size and functionality. | ||
```js | ||
var listener = function (lvl, val, prevLvl, prevVal) { | ||
console.log(lvl, val, prevLvl, prevVal) | ||
} | ||
logger.on('level-change', listener) | ||
logger.level = 'trace' // trigger console message | ||
logger.removeListener('level-change', listener) | ||
logger.level = 'info' // no message, since listener was removed | ||
``` | ||
<a name="levelValues"></a> | ||
### logger.levels.values & pino.levels.values | ||
Returns the mappings of level names to their respective internal number | ||
representation. For example: | ||
```js | ||
pino.levels.values.error === 50 // true | ||
``` | ||
<a name="levelLabels"></a> | ||
### logger.levels.labels & pino.levels.labels | ||
Returns the mappings of level internal level numbers to their string | ||
representations. For example: | ||
```js | ||
pino.levels.labels[50] === 'error' // true | ||
``` | ||
<a name="log_version"></a> | ||
### logger.LOG_VERSION & pino.LOG_VERSION | ||
Read only. Holds the current log format version (as output in the `v` property of each log record). | ||
<a name="reqSerializer"></a> | ||
### pino.stdSerializers.req | ||
Generates a JSONifiable object from the HTTP `request` object passed to | ||
the `createServer` callback of Node's HTTP server. | ||
It returns an object in the form: | ||
```js | ||
{ | ||
pid: 93535, | ||
hostname: 'your host', | ||
level: 30, | ||
msg: 'my request', | ||
time: '2016-03-07T12:21:48.766Z', | ||
v: 0, | ||
req: { | ||
method: 'GET', | ||
url: '/', | ||
headers: { | ||
host: 'localhost:50201', | ||
connection: 'close' | ||
}, | ||
remoteAddress: '::ffff:127.0.0.1', | ||
remotePort: 50202 | ||
} | ||
} | ||
``` | ||
<a name="resSerializer"></a> | ||
### pino.stdSerializers.res | ||
Generates a JSONifiable object from the HTTP `response` object passed to | ||
the `createServer` callback of Node's HTTP server. | ||
It returns an object in the form: | ||
```js | ||
{ | ||
pid: 93581, | ||
hostname: 'myhost', | ||
level: 30, | ||
msg: 'my response', | ||
time: '2016-03-07T12:23:18.041Z', | ||
v: 0, | ||
res: { | ||
statusCode: 200, | ||
header: 'HTTP/1.1 200 OK\r\nDate: Mon, 07 Mar 2016 12:23:18 GMT\r\nConnection: close\r\nContent-Length: 5\r\n\r\n' | ||
} | ||
} | ||
``` | ||
<a name="errSerializer"></a> | ||
### pino.stdSerializers.err | ||
Serializes an `Error` object if passed in as an property. | ||
```js | ||
{ | ||
"pid": 40510, | ||
"hostname": "MBP-di-Matteo", | ||
"level": 50, | ||
"msg": "an error", | ||
"time": 1459433282301, | ||
"v": 1, | ||
"type": "Error", | ||
"stack": "Error: an error\n at Object.<anonymous> (/Users/matteo/Repositories/pino/example.js:16:7)\n at Module._compile (module.js:435:26)\n at Object.Module._extensions..js (module.js:442:10)\n at Module.load (module.js:356:32)\n at Function.Module._load (module.js:313:12)\n at Function.Module.runMain (module.js:467:10)\n at startup (node.js:136:18)\n at node.js:963:3" | ||
} | ||
``` | ||
<a name="pretty"></a> | ||
### pino.pretty([opts]) | ||
Returns a transform stream that formats JSON output into pretty print | ||
output as per the [cli](#cli) tool. | ||
Options: | ||
* `timeTransOnly`, if set to `true`, it will only covert the unix timestamp to | ||
ISO 8601 date format, and reserialize the JSON (equivalent to `pino -t`). | ||
* `formatter`, a custom function to format the line, is passed the JSON | ||
object as an argument and should return a string value | ||
You can use the pretty transformer internally, like so: | ||
```js | ||
'use strict' | ||
var pino = require('pino') | ||
var pretty = pino.pretty() | ||
pretty.pipe(process.stdout) | ||
var log = pino({ | ||
name: 'app', | ||
safe: true | ||
}, pretty) | ||
log.child({ widget: 'foo' }).info('hello') | ||
log.child({ widget: 'bar' }).warn('hello 2') | ||
``` | ||
<a name="extreme"></a> | ||
## Extreme mode explained | ||
In essence, Extreme mode enables extreme performance by buffering log messages and writing them in larger chunks. | ||
This has a couple of important caveats: | ||
* 4KB of spare RAM will be needed for logging | ||
* As opposed to the default mode, there is not a one-to-one relationship between calls to logging methods (e.g. `logger.info`) and writes to a log file (or log stream) | ||
* There is a possibility of the most recently buffered log messages being lost (up to 4KB of logs) | ||
* For instance, a powercut will mean up to 4KB of buffered logs will be lost | ||
* A sigkill (or other untrappable signal) will probably result in the same | ||
* If writing to a stream other than `process.stdout` or `process.stderr`, there is a slight possibility of lost logs or even partially written logs if the OS buffers don't have enough space, or something else is being written to the stream (or maybe some other reason we've not thought of) | ||
* If you supply an alternate stream to the constructor, then that stream must support synchronous writes so that it can be properly flushed on exit. This means the stream must expose its file descriptor via `stream.fd` or `stream._handle.fd`. Usually they have to be native (from core) stream, meaning a TCP/unix socket, a file, or stdout/sderr. If your stream is invalid an `error` event will be emitted on the returned logger, e.g.: | ||
```js | ||
var stream = require('stream') | ||
var pino = require('pino') | ||
var logger = pino({extreme: true}, new stream.Writable({write: function (chunk) { | ||
// do something with chunk | ||
}})) | ||
logger.on('error', function (err) { | ||
console.error('pino logger cannot flush on exit due to provided output stream') | ||
process.exit(1) | ||
}) | ||
``` | ||
So in summary, only use extreme mode if you're doing an extreme amount of logging, and you're happy in some scenarios to lose the most recent logs. | ||
<a name="express"></a> | ||
## How to use Pino with Express | ||
We've got you covered: | ||
```sh | ||
npm install --save express-pino-logger | ||
``` | ||
```js | ||
var app = require('express')() | ||
var pino = require('express-pino-logger')() | ||
app.use(pino) | ||
app.get('/', function (req, res) { | ||
req.log.info('something') | ||
res.send('hello world') | ||
}) | ||
app.listen(3000) | ||
``` | ||
See the [express-pino-logger readme](http://npm.im/express-pino-logger) for more info. | ||
<a name="hapi"></a> | ||
## How to use Pino with Hapi | ||
We've got you covered: | ||
```sh | ||
npm install --save hapi-pino | ||
``` | ||
```js | ||
'use strict' | ||
const Hapi = require('hapi') | ||
const server = new Hapi.Server() | ||
server.connection({ port: 3000 }) | ||
server.route({ | ||
method: 'GET', | ||
path: '/', | ||
handler: function (request, reply) { | ||
request.logger.info('In handler %s', request.path) | ||
return reply('hello world') | ||
} | ||
}) | ||
server.register(require('hapi-pino'), (err) => { | ||
if (err) { | ||
console.error(err) | ||
process.exit(1) | ||
} | ||
server.logger().info('another way for accessing it') | ||
// Start the server | ||
server.start((err) => { | ||
if (err) { | ||
console.error(err) | ||
process.exit(1) | ||
} | ||
}) | ||
}) | ||
``` | ||
See the [hapi-pino readme](http://npm.im/hapi-pino) for more info. | ||
<a name="restify"></a> | ||
## How to use Pino with Restify | ||
We've got you covered: | ||
```sh | ||
npm install --save restify-pino-logger | ||
``` | ||
```js | ||
var server = require('restify').createServer({name: 'server'}) | ||
var 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](http://npm.im/restify-pino-logger) for more info. | ||
<a name="koa"></a> | ||
## How to use Pino with koa | ||
We've got you covered: | ||
### Koa v1 | ||
```sh | ||
npm install --save koa-pino-logger@1 | ||
``` | ||
```js | ||
var app = require('koa')() | ||
var pino = require('koa-pino-logger')() | ||
app.use(pino) | ||
app.use(function * () { | ||
this.log.info('something else') | ||
this.body = 'hello world' | ||
}) | ||
app.listen(3000) | ||
``` | ||
See the [koa-pino-logger v1 readme](https://github.com/pinojs/koa-pino-logger/tree/v1) for more info. | ||
### Koa v2 | ||
```sh | ||
npm install --save koa-pino-logger@2 | ||
``` | ||
```js | ||
var Koa = require('koa') | ||
var app = new Koa() | ||
var 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 v2 readme](https://github.com/pinojs/koa-pino-logger/tree/v2) for more info. | ||
<a name="rotate"></a> | ||
## How do I rotate log files? | ||
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 | ||
} | ||
``` | ||
<a name="redact"></a> | ||
## How do I redact sensitive information?? | ||
Use [pino-noir](http://npm.im/pino-noir), initialize with the key paths you wish to redact and pass the resulting instance in through the `serializers` option | ||
```js | ||
var noir = require('pino-noir') | ||
var pino = require('pino')({ | ||
serializers: noir(['key', 'path.to.key']) | ||
}) | ||
pino.info({ | ||
key: 'will be redacted', | ||
path: { | ||
to: {key: 'sensitive', another: 'thing'} | ||
}, | ||
more: 'stuff' | ||
}) | ||
// {"pid":7306,"hostname":"x","level":30,"time":1475519922198,"key":"[Redacted]","path":{"to":{"key":"[Redacted]","another":"thing"}},"more":"stuff","v":1} | ||
``` | ||
If you have other serializers simply extend: | ||
```js | ||
var noir = require('pino-noir') | ||
var pino = require('pino')({ | ||
serializers: Object.assign( | ||
noir(['key', 'path.to.key']), | ||
{myCustomSerializer: () => {}} | ||
}) | ||
``` | ||
<a name="transports"></a> | ||
## How to use Transports with Pino | ||
Create a separate process and pipe to it. | ||
For example: | ||
```js | ||
var split = require('split2') | ||
var pump = require('pump') | ||
var through = require('through2') | ||
var myTransport = through.obj(function (chunk, enc, cb) { | ||
// do whatever you want here! | ||
console.log(chunk) | ||
cb() | ||
}) | ||
pump(process.stdin, split(JSON.parse), myTransport) | ||
``` | ||
```sh | ||
node my-app-which-logs-stuff-to-stdout.js | node my-transport-process.js | ||
``` | ||
Using transports in the same process causes unnecessary load and slows down Node's single threaded event loop. | ||
If you write a transport, let us know and we will add a link here! | ||
<a name="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 yourapp.js | pino-elasticsearch | ||
``` | ||
Assuming Elasticsearch is running on localhost. | ||
If you wish to connect to an external elasticsearch instance (recommended for production): | ||
* Check that you defined `network.host` in your `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 yourapp.js | pino-elasticsearch --host 192.168.1.42 | ||
``` | ||
Assuming Elasticsearch is running on `192.168.1.42`. | ||
Then, head to your | ||
Kibana instance, and [create an index pattern](https://www.elastic.co/guide/en/kibana/current/setup.html) on `'pino'`, | ||
the default for `pino-elasticsearch`. | ||
[pino-elasticsearch]: https://github.com/pinojs/pino-elasticsearch | ||
<a name="pino-socket"></a> | ||
### pino-socket | ||
[pino-socket][pino-socket] is a transport that will forward logs to a IPv4 | ||
UDP or TCP socket. As an example, use `socat` to fake a listener: | ||
```sh | ||
$ socat -v udp4-recvfrom:6000,fork exec:'/bin/cat' | ||
``` | ||
And then run an application that uses `pino` for logging: | ||
```sh | ||
$ node yourapp.js | pino-socket -p 6000 | ||
``` | ||
You should see the logs from your application on both consoles. | ||
<a name="pino-syslog"></a> | ||
### pino-syslog | ||
[pino-syslog][pino-syslog] is a transport, really a "transform," that converts | ||
*pino's* logs to [RFC3164][rfc3164] compatible log messages. *pino-syslog* does not | ||
forward the logs anywhere, it merely re-writes the messages to `stdout`. But | ||
in combination with *pino-socket*, you can relay logs to a syslog server: | ||
```sh | ||
$ node yourapp.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,"v":1} | ||
``` | ||
[pino-syslog]: https://www.npmjs.com/package/pino-syslog | ||
[rfc3164]: https://tools.ietf.org/html/rfc3164 | ||
#### Logstash | ||
You can also use [pino-socket][pino-socket] to upload logs to | ||
[Logstash][logstash] via: | ||
``` | ||
$ node yourapp.js | pino-socket -a 127.0.0.1 -p 5000 -m tcp | ||
``` | ||
Assuming your 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]. | ||
If you are a Docker fan, you can use | ||
https://github.com/deviantony/docker-elk to setup an ELK stack. | ||
[pino-socket]: https://www.npmjs.com/package/pino-socket | ||
<a name="browser"></a> | ||
## Pino in the browser | ||
Pino is compatible with [`browserify`](http://npm.im) for browser side usage. This can be useful with isomorphic/universal JavaScript code. | ||
Pino is compatible with [`browserify`](http://npm.im) for browser side usage: | ||
This can be useful with isomorphic/universal JavaScript code. | ||
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. | ||
@@ -1016,3 +206,2 @@ | ||
### David Mark Clements | ||
@@ -1038,2 +227,10 @@ | ||
## Contributing | ||
Pino is an **OPEN Open Source Project**. This means that: | ||
> Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. | ||
See the [CONTRIBUTING.md](https://github.com/pinojs/pino/blob/master/CONTRIBUTING.md) file for more details. | ||
<a name="acknowledgements"></a> | ||
@@ -1040,0 +237,0 @@ ## Acknowledgements |
@@ -70,24 +70,2 @@ 'use strict' | ||
test('passing error at level ' + name, function (t) { | ||
t.plan(2) | ||
var err = new Error('myerror') | ||
var instance = pino(sink(function (chunk, enc, cb) { | ||
t.ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()') | ||
delete chunk.time | ||
t.deepEqual(chunk, { | ||
pid: pid, | ||
hostname: hostname, | ||
level: level, | ||
type: 'Error', | ||
msg: err.message, | ||
stack: err.stack, | ||
v: 1 | ||
}) | ||
cb() | ||
})) | ||
instance.level = name | ||
instance[name](err) | ||
}) | ||
test('passing error with a serializer at level ' + name, function (t) { | ||
@@ -296,2 +274,38 @@ t.plan(2) | ||
// https://github.com/pinojs/pino/issues/139 | ||
test('object and format string', function (t) { | ||
var instance = pino(sink(function (chunk, enc, cb) { | ||
delete chunk.time | ||
t.deepEqual(chunk, { | ||
pid: pid, | ||
hostname: hostname, | ||
level: 30, | ||
msg: 'foo bar', | ||
v: 1 | ||
}) | ||
t.end() | ||
cb() | ||
})) | ||
instance.info({}, 'foo %s', 'bar') | ||
}) | ||
test('object and format string property', function (t) { | ||
var instance = pino(sink(function (chunk, enc, cb) { | ||
delete chunk.time | ||
t.deepEqual(chunk, { | ||
pid: pid, | ||
hostname: hostname, | ||
level: 30, | ||
msg: 'foo bar', | ||
answer: 42, | ||
v: 1 | ||
}) | ||
t.end() | ||
cb() | ||
})) | ||
instance.info({ answer: 42 }, 'foo %s', 'bar') | ||
}) | ||
test('correctly strip undefined when returned from toJSON', function (t) { | ||
@@ -298,0 +312,0 @@ t.plan(1) |
@@ -243,3 +243,3 @@ 'use strict' | ||
function fnName (fn) { | ||
var rx = /^\s*function\s*([^\(]*)/i | ||
var rx = /^\s*function\s*([^(]*)/i | ||
var match = rx.exec(fn) | ||
@@ -246,0 +246,0 @@ return match && match[1] |
@@ -140,1 +140,7 @@ 'use strict' | ||
}) | ||
test('flush does nothing without stream mode', function (t) { | ||
var instance = require('..')() | ||
instance.flush() | ||
t.end() | ||
}) |
@@ -29,2 +29,10 @@ 'use strict' | ||
test('the wrong level throws', function (t) { | ||
t.plan(1) | ||
var instance = pino() | ||
t.throws(function () { | ||
instance.level = 'kaboom' | ||
}) | ||
}) | ||
test('set the level by number', function (t) { | ||
@@ -31,0 +39,0 @@ t.plan(4) |
@@ -113,1 +113,63 @@ 'use strict' | ||
}) | ||
test('pino transform prettifies properties', function (t) { | ||
t.plan(1) | ||
var pretty = pino.pretty() | ||
var first = true | ||
pretty.pipe(split(function (line) { | ||
if (first) { | ||
first = false | ||
} else { | ||
t.equal(line, ' a: "b"', 'prettifies the line') | ||
} | ||
return line | ||
})) | ||
var instance = pino(pretty) | ||
instance.info({ a: 'b' }, 'hello world') | ||
}) | ||
test('pino transform treats the name with care', function (t) { | ||
t.plan(1) | ||
var pretty = pino.pretty() | ||
pretty.pipe(split(function (line) { | ||
t.ok(line.match(/\(matteo\/.*$/), 'includes the name') | ||
return line | ||
})) | ||
var instance = pino({ name: 'matteo' }, pretty) | ||
instance.info('hello world') | ||
}) | ||
test('handles `null` input', function (t) { | ||
t.plan(1) | ||
var pretty = pino.pretty() | ||
pretty.pipe(split(function (line) { | ||
t.is(line, 'null') | ||
return line | ||
})) | ||
pretty.write('null') | ||
pretty.end() | ||
}) | ||
test('handles `undefined` input', function (t) { | ||
t.plan(1) | ||
var pretty = pino.pretty() | ||
pretty.pipe(split(function (line) { | ||
t.is(line, 'undefined') | ||
return line | ||
})) | ||
pretty.write('undefined') | ||
pretty.end() | ||
}) | ||
test('handles `true` input', function (t) { | ||
t.plan(1) | ||
var pretty = pino.pretty() | ||
pretty.pipe(split(function (line) { | ||
t.is(line, 'true') | ||
return line | ||
})) | ||
pretty.write('true') | ||
pretty.end() | ||
}) |
Sorry, the diff of this file is not supported yet
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance 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
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 1 instance in 1 package
8
5
98602
18
27
2214
244
3
+ Addedquick-format-unescaped@1.1.2(transitive)
- Removedcore-util-is@^1.0.2
- Removedquick-format@^2.0.4
- Removedquick-format@2.1.0(transitive)
Updatedfast-safe-stringify@^1.1.3
Updatedflatstr@^1.0.4