* Issue #382, pretty print error with null stack (#383)

* fix issue #382 pretty print error with null stack

* prints stack: "null" when error is a property

* errors with a null stack, #382

* Bumped v4.15.4.

* added lastTime meta parameter (#385)

* Bumped v4.16.0.

* Remove CLI and add robust prettifier support

* Use required prettifier directly and tweak documentation

* Address documentation concerns

* Remove chalk

* Add bin with deprecation notice

* Add URL to deprecation notice

* deprecated => removed

* Move split2 to dev dependencies

* Move asMetaWrapper into Pino and update pretty docs
This commit is contained in:
James Sumners 2018-04-13 08:11:40 -04:00 коммит произвёл GitHub
Родитель 420ea7541c
Коммит 145e724a4e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
15 изменённых файлов: 199 добавлений и 1189 удалений

Просмотреть файл

@ -3,15 +3,12 @@
# pino  [![Build Status](https://travis-ci.org/pinojs/pino.svg?branch=master)](https://travis-ci.org/pinojs/pino) [![Coverage Status](https://coveralls.io/repos/github/pinojs/pino/badge.svg?branch=master)](https://coveralls.io/github/pinojs/pino?branch=master) [![TypeScript definitions on DefinitelyTyped](http://definitelytyped.org/badges/standard.svg)](http://definitelytyped.org)
[Extremely fast](#benchmarks) node.js logger, inspired by Bunyan.
It also includes a shell utility to pretty-print its log files.
![cli](demo.png)
* [Installation](#install)
* [Usage](#usage)
* [Benchmarks](#benchmarks)
* [API ⇗](docs/API.md)
* [CLI ⇗](docs/cli.md)
* [Pretty Printing ⇗](docs/pretty.md)
* [Extreme mode explained ⇗](docs/extreme.md)
* [Pino Howtos ⇗](docs/howtos.md)
* [Transports with Pino](#transports)
@ -78,6 +75,11 @@ This produces:
```
If you have the [`pino-pretty`](https://github.com/pinojs/pino-pretty) module
installed then you can make these logs easier to read:
![pretty demo](pretty-demo.png)
<a name="benchmarks"></a>
## Benchmarks

65
bin.js Executable file → Normal file
Просмотреть файл

@ -1,59 +1,6 @@
#! /usr/bin/env node
'use strict'
var pretty = require('./pretty')
var fs = require('fs')
module.exports = pretty
if (arg('-h') || arg('--help')) {
usage().pipe(process.stdout)
} else if (arg('-v') || arg('--version')) {
console.log(require('./package.json').version)
} else {
process.stdin.pipe(pretty({
timeTransOnly: arg('-t'),
levelFirst: arg('-l'),
forceColor: arg('-c'),
messageKey: argWithParam('-m'),
dateFormat: argWithParam('--dateFormat'),
errorProps: paramToArray(argWithParam('--errorProps')),
errorLikeObjectKeys: paramToArray(argWithParam('--errorLikeObjectKeys')),
localTime: arg('--localTime')
})).pipe(process.stdout)
if (!process.stdin.isTTY && !fs.fstatSync(process.stdin.fd).isFile()) {
process.once('SIGINT', function noOp () {})
}
}
function usage () {
var help = require('path').join(__dirname, 'usage.txt')
return fs.createReadStream(help)
}
function arg (s) {
return !!~process.argv.indexOf(s)
}
function argWithParam (s) {
if (!arg(s)) {
return
}
var argIndex = process.argv.indexOf(s) + 1
var argValue = process.argv.length > argIndex &&
process.argv[argIndex]
if (!argValue) {
throw new Error(s + ' flag provided without a string argument')
}
return argValue
}
function paramToArray (param) {
if (!param) {
return
}
return param.split(/\s?,\s?/)
}
#!/usr/bin/env node
console.error(
'`pino` cli has been removed. Use `pino-pretty` cli instead.\n' +
'\nSee: https://github.com/pinojs/pino-pretty'
)
process.exit(1)

Двоичные данные
demo.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 391 KiB

Просмотреть файл

@ -1,7 +1,6 @@
# Table of Contents
+ [pino](#constructor)
+ [pino.pretty](#pretty)
+ [Logger Instance](#logger)
* [.pino](#version)
* [.child](#child)
@ -73,8 +72,13 @@
* `levelVal` (integer): when defining a custom log level via `level`, set to an
integer value to define the new level. Default: `undefined`.
* `messageKey` (string): the string key for the 'message' in the JSON object. Default `msg`.
* `prettyPrint` (boolean|object): enables [pino.pretty](#pretty). This is intended for non-production
configurations. This may be set to a configuration object as outlined in [pino.pretty](#pretty). Default: `false`.
* `prettyPrint` (boolean|object): enables pretty printing log logs. This is intended for non-production
configurations. This may be set to a configuration object as outlined in the
[`pino-pretty` documentation](https://github.com/pinojs/pino-pretty).
The options object may additionally contain a `prettifier` property to define
which prettifier module to use. When not present, `prettifier` defaults to
`'pino-pretty'`. Regardless of the value, the specified prettifier module
must be installed as a separate dependency. Default: `false`.
* `onTerminated` (function): this function will be invoked during process shutdown when `extreme` is set to `true`.
The signature of the function is `onTerminated(eventName, err)`. If you do not specify a function, Pino will
invoke `process.exit(0)` when no error has occurred, and `process.exit(1)` otherwise. If you do specify a function,
@ -108,49 +112,6 @@ var logger = pino({
### Discussion:
Returns a new [logger](#logger) instance.
<a id="pretty"></a>
## .pretty([options])
### Parameters:
+ `options` (object):
* `timeTransOnly` (boolean): 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` (function): a custom function to format the line. It's passed 2 arguments,
JSON object log data and an options object
that [exposes utility functions](https://github.com/pinojs/pino/blob/master/pretty.js#L110).
It should return a string value.
* `levelFirst` (boolean): if set to `true`, it will print the name of the log
level as the first field in the log line. Default: `false`.
* `messageKey` (string): the key in the JSON object to use as the highlighted
message. Default: `msg`.
* `forceColor` (boolean): if set to `true`, will add color information to the formatted output
message. Default: `false`.
* `crlf` (boolean): emit `\r\n` instead of `\n`. Default: `false`.
* `errorLikeObjectKeys` (array): error-like objects containing stack traces that should be prettified. Default: `['err', 'error']`.
### Example:
```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')
```
### Discussion:
Provides access to the [CLI](cli.md) log prettifier as an API.
This can also be enabled via the [constructor](#constructor) by setting the
`prettyPrint` option to either `true` or a configuration object described
in this section.
<a id="logger"></a>
# Logger

Просмотреть файл

@ -1,131 +0,0 @@
# CLI
Pino provides a command line interface that can be used to parse Pino log
lines into an easy to read format.
To use the command line tool, we can install `pino` globally:
```sh
npm install -g pino
```
The pretty-printed output will highlight the message value of the input JSON. By
default, Pino provides this message value at the `msg` key. A custom key can be
specified with `-m <key>`.
`pino -m fooMessage` will transform this:
```js
{"pid":14139,"hostname":"MacBook-Pro-3.home","level":30,"fooMessage":"hello world","time":1457537229339,"v":1}
```
Into this:
```sh
[2016-03-09T15:27:09.339Z] INFO (14139 on MacBook-Pro-3.home): hello world
```
There are also two transformer flags:
+ `-t` that converts Epoch timestamps to ISO timestamps.
```sh
cat log | pino -t
```
+ `-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":1}
```
Into this:
```js
{"pid":14139,"hostname":"MacBook-Pro-3.home","level":30,"msg":"hello world","time":"2016-03-09T15:27:09.339Z","v":1}
```
`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
```
If you would like to enforce the output to be color encoded you can specify the `-c` flag
`cat log | pino -c` will transform this:
```js
{"pid":14139,"hostname":"MacBook-Pro-3.home","level":30,"fooMessage":"hello world","time":1457537229339,"v":1}
```
Into this:
```sh
[2017-04-25T17:32:09.662Z] INFO (24280 on SP2): hello world
```
If an instance of `Error` is logged, Pino adds `"type":"Error"` to the logged JSON.
Thus, when prettifying the output, Pino will transform the JSON:
```js
{"level":50,"time":1457537229339,"msg":"Error message.","pid":44127,"hostname":"MacBook-Pro-3.home","type":"Error","stack":"Stack of the error","statusCode":500,"dataBaseSpecificError":{"errorType":"some database error type","erroMessage":"some database error message","evenMoreSpecificStuff":{"someErrorRelatedObject":"error"}},"v":1}
```
To:
```sh
ERROR [2016-03-09T15:27:09.339Z] (44127 on MacBook-Pro-3.home): Error message.
Stack of the error
```
To log additional properties of `Error` objects, supply the `--errorProps <properties>` flag.
For example, `pino --errorProps statusCode` will transform:
```js
{"level":50,"time":1457537229339,"msg":"Error message.","pid":44127,"hostname":"MacBook-Pro-3.home","type":"Error","stack":"Stack of the error","statusCode":500,"dataBaseSpecificError":{"errorType":"some database error type","erroMessage":"some database error message","evenMoreSpecificStuff":{"someErrorRelatedObject":"error"}},"v":1}
```
To:
```sh
ERROR [2016-03-09T15:27:09.339Z] (44127 on MacBook-Pro-3.home): Error message.
Stack of the error
statusCode: 500
```
In order to print all nested properties of `Error` objects, you can use `--errorProps` flag with `*` property.
Note: you must quote or escape the `*` (asterisk) to avoid shell expansion.
`pino --errorProps '*'` will transform:
```js
{"level":50,"time":1457537229339,"msg":"Error message.","pid":44127,"hostname":"MacBook-Pro-3.home","type":"Error","stack":"Stack of the error","statusCode":500,"dataBaseSpecificError":{"errorType":"some database error type","erroMessage":"some database error message","evenMoreSpecificStuff":{"someErrorRelatedObject":"error"}},"v":1}
```
To:
```sh
[2016-03-09T15:27:09.339Z] ERROR (44127 on MacBook-Pro-3.home): Error message.
Stack of the error
statusCode: 500
dataBaseSpecificError: {
errorType: "some database error type"
erroMessage: "some database error message"
evenMoreSpecificStuff: {
"someErrorRelatedObject": "error"
}
}
```

86
docs/pretty.md Normal file
Просмотреть файл

@ -0,0 +1,86 @@
# Pretty Printing
By default, Pino log lines are newline delimited JSON (NDJSON). This is perfect
for production usage and long term storage. It's not so great for development
environments. Thus, Pino logs can be prettified by using a Pino prettifier
module like [`pino-pretty`][pp]:
```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 which conforms to the Pino
[metadata streams](API.md#metadata) API.
The API requires modules provide a factory function which returns a prettifier
function. This prettifier function must accept either a string of NDJSON or
a Pino log object. A psuedo-example of such a prettifier is:
```js
module.exports = function myPrettifier (options) {
// Deal with whatever options are supplied.
return function prettifier (inputData) {
let logObject
if (typeof inputData === 'string') {
const parsedData = someJsonParser(inputData)
logObject = (isPinoLog(parsedData)) ? parsedData : undefined
} else if (isObject(inputData) && isPinoLog(inputData)) {
logObject = inputData
}
if (!logObject) return inputData
// implement prettification
}
function isObject (input) {
return Object.prototype.toString.apply(input) === '[object Object]'
}
function isPinoLog (log) {
return log && (log.hasOwnProperty('v') && log.v === 1)
}
}
```
The reference implementation of such a module is the [`pino-pretty`][pp] module.
To learn more about creating your own prettifier module, learn from the
`pino-pretty` source code.
### API Example
> #### NOTE:
> For general usage, it is highly recommended that you pipe logs 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 --save pino-pretty`.
1. Instantiate the logger with pretty printing enabled:
```js
const pino = require('pino')
const log = pino({
prettyPrint: {
levelFirst: true
},
prettifier: require('pino-pretty')
})
```
Note: the default prettifier module is `pino-pretty`, so the preceeding
example could be:
```js
const pino = require('pino')
const log = pino({
prettyPrint: {
levelFirst: true
}
})
```
See the [`pino-pretty` documentation][pp] for more information on the options
that can be passed via `prettyPrint`.
[pp]: https://github.com/pinojs/pino-pretty

Просмотреть файл

@ -27,6 +27,41 @@ function applyOptions (self, opts) {
self._setLevel(opts.level)
}
function asMetaWrapper (pretty, dest) {
const parsed = Symbol('parsedChindings')
if (!dest) {
dest = process.stdout
} else if (!dest.write) {
throw new Error('the destination must be writable')
}
return {
[Symbol.for('needsMetadata')]: true,
lastLevel: 0,
lastMsg: null,
lastObj: null,
lastLogger: null,
write (chunk) {
var chindings = this.lastLogger[parsed]
if (!chindings) {
chindings = JSON.parse('{"v":1' + this.lastLogger.chindings + '}')
this.lastLogger[parsed] = chindings
}
const obj = Object.assign({
level: this.lastLevel,
msg: this.lastMsg,
time: this.lastTime
}, chindings, this.lastObj)
const formatted = pretty(obj)
dest.write(formatted)
}
}
}
function defineLevelsProperty (onObject) {
Object.defineProperty(onObject, 'levels', {
value: {
@ -98,6 +133,7 @@ module.exports = {
noop: noop,
copy: copy,
applyOptions: applyOptions,
asMetaWrapper: asMetaWrapper,
defineLevelsProperty: defineLevelsProperty,
streamIsBlockable: streamIsBlockable,
genLog: genLog

Просмотреть файл

@ -4,9 +4,6 @@
"description": "super fast, all natural json logger",
"main": "pino.js",
"browser": "./browser.js",
"bin": {
"pino": "./bin.js"
},
"files": [
"pino.js",
"bin.js",
@ -32,6 +29,9 @@
"bench-grandchild": "node benchmarks/runbench grandchild",
"bench-conception": "node benchmarks/runbench conception"
},
"bin": {
"pino": "./bin.js"
},
"precommit": "test",
"repository": {
"type": "git",
@ -64,9 +64,11 @@
"fresh-require": "^1.0.3",
"log": "^1.4.0",
"loglevel": "^1.6.1",
"pino-pretty": "^1.0.0",
"pre-commit": "^1.2.2",
"proxyquire": "^2.0.1",
"snazzy": "^7.1.1",
"split2": "^2.2.0",
"standard": "^11.0.1",
"steed": "^1.1.3",
"tap": "^11.1.3",
@ -76,13 +78,11 @@
"zuul": "^3.11.1"
},
"dependencies": {
"chalk": "^2.3.2",
"fast-json-parse": "^1.0.3",
"fast-safe-stringify": "^1.2.3",
"flatstr": "^1.0.5",
"pino-std-serializers": "^2.0.0",
"pump": "^3.0.0",
"quick-format-unescaped": "^1.1.2",
"split2": "^2.2.0"
"quick-format-unescaped": "^1.1.2"
}
}

20
pino.js
Просмотреть файл

@ -6,9 +6,7 @@ var stringifySafe = require('fast-safe-stringify')
var serializers = require('pino-std-serializers')
var fs = require('fs')
var util = require('util')
var pump = require('pump')
var flatstr = require('flatstr')
var pretty = require('./pretty')
var events = require('./lib/events')
var levels = require('./lib/levels')
var tools = require('./lib/tools')
@ -274,6 +272,18 @@ Object.defineProperty(pinoPrototype, 'isLevelEnabled', {
value: isLevelEnabled
})
function getPrettyStream (opts, prettifier) {
if (prettifier && typeof prettifier === 'function') {
return tools.asMetaWrapper(prettifier(opts), process.stdout)
}
try {
var prettyFactory = require('pino-pretty')
return tools.asMetaWrapper(prettyFactory(opts), process.stdout)
} catch (e) {
throw Error('Missing `pino-pretty` module: `pino-pretty` must be installed separately')
}
}
function pino (opts, stream) {
var iopts = opts
var istream = stream
@ -288,10 +298,7 @@ function pino (opts, stream) {
if (!isStdout && iopts.prettyPrint) throw Error('cannot enable pretty print when stream is not process.stdout')
if (iopts.prettyPrint) {
var prettyOpts = Object.assign({ messageKey: iopts.messageKey }, iopts.prettyPrint)
var pstream = pretty(prettyOpts)
pump(pstream, process.stdout, function (err) {
if (err) instance.emit('error', err)
})
var pstream = getPrettyStream(prettyOpts, iopts.prettifier)
istream = pstream
}
@ -400,7 +407,6 @@ Object.defineProperty(module.exports.stdSerializers, 'wrapRespnonseSerializer',
})
module.exports.stdTimeFunctions = Object.assign({}, time)
module.exports.pretty = pretty
Object.defineProperty(
module.exports,
'LOG_VERSION',

Двоичные данные
pretty-demo.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 124 KiB

328
pretty.js
Просмотреть файл

@ -1,328 +0,0 @@
'use strict'
var split = require('split2')
var Parse = require('fast-json-parse')
var chalk = require('chalk')
var levels = {
default: 'USERLVL',
60: 'FATAL',
50: 'ERROR',
40: 'WARN',
30: 'INFO',
20: 'DEBUG',
10: 'TRACE'
}
var standardKeys = [
'pid',
'hostname',
'name',
'level',
'time',
'v'
]
var defaultErrorLikeObjectKeys = [
'err',
'error'
]
var defaultMessageKey = 'msg'
function toTimezoneOffset (aMinTimeoffset) {
// +/- minute timeoffset
var tz = aMinTimeoffset || new Date().getTimezoneOffset()
var tmp = Math.abs(tz)
var offset = _lpadzero(String(Math.floor(tmp / 60)), 2) + ':' + _lpadzero(String(tmp % 60), 2)
return tz > 0 ? '-' + offset : '+' + offset
}
function _lpadzero (aTarget, aLength, aPadChar) {
var char = aPadChar || '0'
var targetStr = aTarget.toString()
var times = aLength - targetStr.length
var padding = ''
while ((times--) > 0) {
padding += char
}
return padding + targetStr
}
function withSpaces (value, eol) {
var lines = value.split(/\r?\n/)
for (var i = 1; i < lines.length; i++) {
lines[i] = ' ' + lines[i]
}
return lines.join(eol)
}
function filter (value, messageKey, eol, errorLikeObjectKeys, excludeStandardKeys) {
errorLikeObjectKeys = errorLikeObjectKeys || []
var keys = Object.keys(value)
var filteredKeys = [messageKey]
if (excludeStandardKeys !== false) {
filteredKeys = filteredKeys.concat(standardKeys)
}
var result = ''
for (var i = 0; i < keys.length; i++) {
if (errorLikeObjectKeys.indexOf(keys[i]) !== -1) {
var arrayOfLines = (' ' + keys[i] + ': ' + withSpaces(JSON.stringify(value[keys[i]], null, 2), eol) + eol).split('\n')
for (var j = 0; j < arrayOfLines.length; j++) {
if (j !== 0) {
result += '\n'
}
var line = arrayOfLines[j]
if (/^\s*"stack"/.test(line)) {
var matches = /^(\s*"stack":)\s*"(.*)",?$/.exec(line)
if (matches) {
if (matches.length === 3) {
var indentSize = /^\s*/.exec(line)[0].length + 4
var indentation = Array(indentSize + 1).join(' ')
result += matches[1] + '\n' + indentation +
matches[2].replace(/\\n/g, '\n' + indentation)
}
} else {
result += line
}
} else {
result += line
}
}
} else if (filteredKeys.indexOf(keys[i]) < 0) {
result += ' ' + keys[i] + ': ' + withSpaces(JSON.stringify(value[keys[i]], null, 2), eol) + eol
}
}
return result
}
function isPinoLine (line) {
return line && (line.hasOwnProperty('v') && line.v === 1)
}
function pretty (opts) {
var timeTransOnly = opts && opts.timeTransOnly
var formatter = opts && opts.formatter
var dateFormat = opts && opts.dateFormat
var errorProps = opts && opts.errorProps
var errorLikeObjectKeys = opts && opts.errorLikeObjectKeys
var localTime = opts && opts.localTime
var levelFirst = opts && opts.levelFirst
var messageKey = opts && opts.messageKey
var forceColor = opts && opts.forceColor
var eol = opts && opts.crlf ? '\r\n' : '\n'
messageKey = messageKey || defaultMessageKey
errorLikeObjectKeys = errorLikeObjectKeys || defaultErrorLikeObjectKeys
var stream = split(mapLine)
var ctx
var levelColors
var pipe = stream.pipe
stream.pipe = function (dest, opts) {
ctx = new chalk.constructor({
enabled: !!((chalk.supportsColor && dest.isTTY) || forceColor)
})
if (forceColor && ctx.level === 0) {
ctx.level = 1
}
levelColors = {
default: ctx.white,
60: ctx.bgRed,
50: ctx.red,
40: ctx.yellow,
30: ctx.green,
20: ctx.blue,
10: ctx.grey
}
return pipe.call(stream, dest, opts)
}
return stream
function mapLine (line) {
var parsed = new Parse(line)
var value = parsed.value
if (parsed.err || !isPinoLine(value)) {
// pass through
return line + eol
}
if (timeTransOnly) {
value.time = (localTime)
? asLocalISODate(value.time, dateFormat)
: asISODate(value.time, dateFormat)
return JSON.stringify(value) + eol
}
line = (levelFirst)
? asColoredLevel(value) + ' ' + formatTime(value)
: formatTime(value, ' ') + asColoredLevel(value)
if (formatter) {
return opts.formatter(value, {
prefix: line,
chalk: ctx,
withSpaces: withSpaces,
filter: filter,
formatTime: formatTime,
asColoredText: asColoredText,
asColoredLevel: asColoredLevel
}) + eol
}
if (value.name || value.pid || value.hostname) {
line += ' ('
if (value.name) {
line += value.name
}
if (value.name && value.pid) {
line += '/' + value.pid
} else if (value.pid) {
line += value.pid
}
if (value.hostname) {
line += ' on ' + value.hostname
}
line += ')'
}
line += ': '
if (value[messageKey]) {
line += ctx.cyan(value[messageKey])
}
line += eol
if (value.type === 'Error') {
line += ' ' + withSpaces(value.stack, eol) + eol
var propsForPrint
if (errorProps && errorProps.length > 0) {
// don't need print these props for 'Error' object
var excludedProps = standardKeys.concat([messageKey, 'type', 'stack'])
if (errorProps[0] === '*') {
// print all value props excluding 'excludedProps'
propsForPrint = Object.keys(value).filter(function (prop) {
return excludedProps.indexOf(prop) < 0
})
} else {
// print props from 'errorProps' only
// but exclude 'excludedProps'
propsForPrint = errorProps.filter(function (prop) {
return excludedProps.indexOf(prop) < 0
})
}
for (var i = 0; i < propsForPrint.length; i++) {
var key = propsForPrint[i]
if (value.hasOwnProperty(key)) {
if (value[key] instanceof Object) {
// call 'filter' with 'excludeStandardKeys' = false
// because nested property might contain property from 'standardKeys'
line += key + ': {' + eol + filter(value[key], '', eol, errorLikeObjectKeys, false) + '}' + eol
} else {
line += key + ': ' + value[key] + eol
}
}
}
}
} else {
line += filter(value, messageKey, eol, errorLikeObjectKeys)
}
return line
}
function asISODate (time, dateFormat) {
if (dateFormat) {
return asLocalISODate(time, dateFormat, 0)
} else {
return new Date(time).toISOString()
}
}
function asLocalISODate (aTime, aFormat, aMinuteTZ) {
var time = aTime
var format = aFormat || 'YYYY-MM-DDThh:mm:ss.SSSTZ'
var date = new Date(time)
// make independent of the system timezone
var tzOffset = (aMinuteTZ === undefined)
? date.getTimezoneOffset()
: aMinuteTZ
date.setUTCMinutes(date.getUTCMinutes() - tzOffset)
var year = format.indexOf('YYYY') > -1
? date.getUTCFullYear()
: date.getUTCFullYear().toString().slice(2, 4)
var month = _lpadzero(date.getUTCMonth() + 1, 2)
var day = _lpadzero(date.getUTCDate(), 2)
var hour = _lpadzero(date.getUTCHours(), 2)
var minute = _lpadzero(date.getUTCMinutes(), 2)
var second = _lpadzero(date.getUTCSeconds(), 2)
var milli = _lpadzero(date.getUTCMilliseconds(), 3)
date.setUTCMinutes(date.getUTCMinutes() + tzOffset)
return format
.replace(/Y{1,4}/g, year)
.replace(/MM/g, month)
.replace(/DD/g, day)
.replace(/hh/g, hour)
.replace(/mm/g, minute)
.replace(/ss/g, second)
.replace(/SSS/g, milli)
.replace(/TZ/g, toTimezoneOffset(tzOffset))
}
function formatTime (value, after) {
after = after || ''
try {
if (!value || !value.time) {
return ''
} else {
return '[' + ((localTime)
? asLocalISODate(value.time, dateFormat)
: asISODate(value.time, dateFormat)) + ']' + after
}
} catch (_) {
return ''
}
}
function asColoredLevel (value) {
return asColoredText(value, levelColors.hasOwnProperty(value.level) ? levels[value.level] : levels.default)
}
function asColoredText (value, text) {
if (levelColors.hasOwnProperty(value.level)) {
return levelColors[value.level](text)
} else {
return levelColors.default(text)
}
}
}
module.exports = pretty

Просмотреть файл

@ -2,7 +2,6 @@
var test = require('tap').test
var pino = require('../')
var pretty = require('../pretty')
var writer = require('flush-write-stream')
function capture () {
@ -35,23 +34,3 @@ test('pino can log CRLF', function (t) {
t.ok(/foo[^\n]+\r\n[^\n]+bar[^\n]+\r\n/.test(stream.data))
t.end()
})
test('pretty can log CRLF', function (t) {
t.plan(1)
var prettier = pretty({
crlf: true,
formatter: function (data) {
return data.msg
}
})
var stream = capture()
prettier.pipe(stream)
var logger = pino(prettier)
logger.info('foo')
logger.info('bar')
t.is(stream.data, 'foo\r\nbar\r\n')
t.end()
})

6
test/fixtures/pretty/prettyFactory.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,6 @@
global.process = { __proto__: process, pid: 123456 }
Date.now = function () { return 1459875739796 }
require('os').hostname = function () { return 'abcdefghijklmnopqr' }
var pino = require(require.resolve('./../../../'))
var log = pino({prettyPrint: {levelFirst: true}, prettifier: require('pino-pretty')})
log.info('h')

Просмотреть файл

@ -2,398 +2,9 @@
var test = require('tap').test
var pino = require('../')
var pretty = require('../pretty')
var os = require('os')
var path = require('path')
var writeStream = require('flush-write-stream')
var fork = require('child_process').fork
var split = require('split2')
var hostname = os.hostname()
var serializers = require('pino-std-serializers')
test('pino transform prettifies', function (t) {
t.plan(4)
var prettier = pretty()
prettier.pipe(split(function (line) {
t.ok(line.match(/.*hello world$/), 'end of line matches')
t.ok(line.match(/(?!^)INFO.*/), 'includes level')
t.ok(line.indexOf('' + process.pid) > 0, 'includes pid')
t.ok(line.indexOf('' + hostname) > 0, 'includes hostname')
return line
}))
var instance = pino(prettier)
instance.info('hello world')
})
test('pino pretty moves level to start on flag', function (t) {
t.plan(4)
var prettier = pretty({ levelFirst: true })
prettier.pipe(split(function (line) {
t.ok(line.match(/.*hello world$/), 'end of line matches')
t.ok(line.match(/^INFO.*/), 'level is at start of line')
t.ok(line.indexOf('' + process.pid) > 0, 'includes pid')
t.ok(line.indexOf('' + hostname) > 0, 'includes hostname')
return line
}))
var instance = pino(prettier)
instance.info('hello world')
})
test('pino pretty force color on flag', function (t) {
t.plan(1)
var prettier = pretty({ forceColor: true })
prettier.pipe(split(function (line) {
t.ok(line.match(/.*\u001b\[32mINFO\u001b\[39m.*\u001b\[36mhello world\u001b\[39m$/), 'color coding information is encoded in the line')
return line
}))
var instance = pino(prettier)
instance.info('hello world')
})
test('pino transform can just parse the dates', function (t) {
t.plan(1)
var prettier = pretty({ timeTransOnly: true })
prettier.pipe(split(function (line) {
var obj = JSON.parse(line)
t.ok(typeof obj.time === 'string', 'time is a string')
return line
}))
var instance = pino(prettier)
instance.info('hello world')
})
test('pino transform can format with a custom function', function (t) {
t.plan(8)
var prettier = pretty({ formatter: function (line, opts) {
t.ok(opts.prefix.indexOf('INFO') > -1, 'prefix contains level')
t.ok(typeof opts.chalk.white === 'function', 'chalk instance')
t.ok(typeof opts.withSpaces === 'function', 'withSpaces function')
t.ok(typeof opts.filter === 'function', 'filter function')
t.ok(typeof opts.formatTime === 'function', 'formatTime function')
t.ok(typeof opts.asColoredText === 'function', 'asColoredText function')
t.ok(typeof opts.asColoredLevel === 'function', 'asColoredLevel function')
return 'msg: ' + line.msg + ', foo: ' + line.foo
} })
prettier.pipe(split(function (line) {
t.ok(line === 'msg: hello world, foo: bar', 'line matches')
return line
}))
var instance = pino(prettier)
instance.info({foo: 'bar'}, 'hello world')
})
test('pino transform prettifies Error', function (t) {
var prettier = pretty()
var err = new Error('hello world')
var expected = err.stack.split('\n')
expected.unshift(err.message)
t.plan(expected.length)
prettier.pipe(split(function (line) {
t.ok(line.indexOf(expected.shift()) >= 0, 'line matches')
return line
}))
var instance = pino(prettier)
instance.info(err)
})
function getIndentLevel (str) {
return (/^\s*/.exec(str) || [''])[0].length
}
test('pino transform prettifies Error in property within errorLikeObjectKeys', function (t) {
var prettier = pretty({
errorLikeObjectKeys: ['err']
})
var err = new Error('hello world')
var expectedTraces = err.stack.split('\n').slice(1)
t.plan(expectedTraces.length * 2)
var i = 0
var currentTrace = ''
var currentStack = ''
prettier.pipe(split(function (line) {
if (/^\s*"stack"/.test(line)) {
currentStack = line
}
if (/^\s*at/.test(line)) {
currentTrace = expectedTraces.shift()
t.ok(line.indexOf(currentTrace) >= 0, `${i} line matches`)
t.ok(getIndentLevel(line) > getIndentLevel(currentStack), `${i} proper indentation`)
}
i++
return line
}))
var instance = pino({ serializers: { err: serializers.err } }, prettier)
instance.info({ err })
})
test('pino transform prettifies Error in property within errorLikeObjectKeys when stack is not the last property', function (t) {
var prettier = pretty({
errorLikeObjectKeys: ['err']
})
var err = new Error('hello world')
err.anotherField = 'dummy value'
var expectedTraces = err.stack.split('\n').slice(1)
t.plan(expectedTraces.length * 2)
var i = 0
var currentTrace = ''
var currentStack = ''
prettier.pipe(split(function (line) {
if (/^\s*"stack"/.test(line)) {
currentStack = line
}
if (/^\s*at/.test(line)) {
currentTrace = expectedTraces.shift()
t.ok(line.indexOf(currentTrace) >= 0, `${i} line matches`)
t.ok(getIndentLevel(line) > getIndentLevel(currentStack), `${i} proper indentation`)
}
i++
return line
}))
var instance = pino({ serializers: { err: serializers.err } }, prettier)
instance.info({ err })
})
test('pino transform preserve output if not valid JSON', function (t) {
t.plan(1)
var prettier = pretty()
var lines = []
prettier.pipe(split(function (line) {
lines.push(line)
return line
}))
prettier.write('this is not json\nit\'s just regular output\n')
prettier.end()
t.deepEqual(lines, ['this is not json', 'it\'s just regular output'], 'preserved lines')
})
test('handles missing time', function (t) {
t.plan(1)
var prettier = pretty()
var lines = []
prettier.pipe(split(function (line) {
lines.push(line)
return line
}))
prettier.write('{"hello":"world"}')
prettier.end()
t.deepEqual(lines, ['{"hello":"world"}'], 'preserved lines')
})
test('handles missing pid, hostname and name', function (t) {
t.plan(1)
var prettier = pretty()
prettier.pipe(split(function (line) {
t.ok(line.match(/\[.*\] INFO: hello world/), 'line does not match')
return line
}))
var instance = pino({ base: null }, prettier)
instance.info('hello world')
})
test('handles missing pid', function (t) {
t.plan(1)
var name = 'test'
var msg = 'hello world'
var regex = new RegExp('\\[.*\\] INFO \\(' + name + ' on ' + hostname + '\\): ' + msg)
var prettier = pretty()
prettier.pipe(split(function (line) {
t.ok(regex.test(line), 'line does not match')
return line
}))
var opts = {
base: {
name: name,
hostname: hostname
}
}
var instance = pino(opts, prettier)
instance.info(msg)
})
test('handles missing hostname', function (t) {
t.plan(1)
var name = 'test'
var msg = 'hello world'
var regex = new RegExp('\\[.*\\] INFO \\(' + name + '/' + process.pid + '\\): ' + msg)
var prettier = pretty()
prettier.pipe(split(function (line) {
t.ok(regex.test(line), 'line does not match')
return line
}))
var opts = {
base: {
name: name,
pid: process.pid
}
}
var instance = pino(opts, prettier)
instance.info(msg)
})
test('handles missing name', function (t) {
t.plan(1)
var msg = 'hello world'
var regex = new RegExp('\\[.*\\] INFO \\(' + process.pid + ' on ' + hostname + '\\): ' + msg)
var prettier = pretty()
prettier.pipe(split(function (line) {
t.ok(regex.test(line), 'line does not match')
return line
}))
var opts = {
base: {
hostname: hostname,
pid: process.pid
}
}
var instance = pino(opts, prettier)
instance.info(msg)
})
test('pino transform prettifies properties', function (t) {
t.plan(1)
var prettier = pretty()
var first = true
prettier.pipe(split(function (line) {
if (first) {
first = false
} else {
t.equal(line, ' a: "b"', 'prettifies the line')
}
return line
}))
var instance = pino(prettier)
instance.info({ a: 'b' }, 'hello world')
})
test('pino transform prettifies nested properties', function (t) {
t.plan(5)
var expectedLines = [
undefined,
' a: {',
' "b": {',
' "c": "d"',
' }',
' }'
]
var prettier = pretty()
prettier.pipe(split(function (line) {
var expectedLine = expectedLines.shift()
if (expectedLine !== undefined) {
t.equal(line, expectedLine, 'prettifies the line')
}
}))
var instance = pino(prettier)
instance.info({ a: { b: { c: 'd' } } }, 'hello world')
})
test('pino transform treats the name with care', function (t) {
t.plan(1)
var prettier = pretty()
prettier.pipe(split(function (line) {
t.ok(line.match(/\(matteo\/.*$/), 'includes the name')
return line
}))
var instance = pino({ name: 'matteo' }, prettier)
instance.info('hello world')
})
test('handles `null` input', function (t) {
t.plan(1)
var prettier = pretty()
prettier.pipe(split(function (line) {
t.is(line, 'null')
return line
}))
prettier.write('null')
prettier.end()
})
test('handles `undefined` input', function (t) {
t.plan(1)
var prettier = pretty()
prettier.pipe(split(function (line) {
t.is(line, 'undefined')
return line
}))
prettier.write('undefined')
prettier.end()
})
test('handles `true` input', function (t) {
t.plan(1)
var prettier = pretty()
prettier.pipe(split(function (line) {
t.is(line, 'true')
return line
}))
prettier.write('true')
prettier.end()
})
test('accept customLogLevel', function (t) {
t.plan(1)
var prettier = pretty()
prettier.pipe(split(function (line) {
t.ok(line.indexOf('USERLVL') > 0, 'include custom level')
return line
}))
var instance = pino({level: 'testCustom', levelVal: 35}, prettier)
instance.testCustom('test message')
})
test('can be enabled via constructor', function (t) {
t.plan(1)
@ -425,22 +36,19 @@ test('can be enabled via constructor with pretty configuration', function (t) {
})
})
test('works without time', function (t) {
t.plan(4)
var prettier = pretty()
prettier.pipe(split(function (line) {
t.ok(line.match(/.*hello world$/), 'end of line matches')
t.ok(line.match(/^INFO.*/), 'includes level')
t.ok(line.indexOf('' + process.pid) > 0, 'includes pid')
t.ok(line.indexOf('' + hostname) > 0, 'includes hostname')
return line
test('can be enabled via constructor with prettifier', function (t) {
t.plan(1)
var actual = ''
var child = fork(path.join(__dirname, 'fixtures', 'pretty', 'prettyFactory.js'), {silent: true})
child.stdout.pipe(writeStream(function (s, enc, cb) {
actual += s
cb()
}))
var instance = pino({
timestamp: null
}, prettier)
instance.info('hello world')
child.on('close', function () {
t.notEqual(actual.match(/^INFO.*h/), null)
})
})
test('throws error when enabled with stream specified', function (t) {
@ -456,139 +64,3 @@ test('does not throw error when enabled with stream specified', function (t) {
pino({prettyPrint: true}, process.stdout)
t.end()
})
test('pino pretty localTime flag', function (t) {
t.plan(6)
var prettier = pretty({ localTime: true })
prettier.pipe(split(function (line) {
var localTime = line.slice(line.indexOf('[') + 1, line.indexOf(']'))
var msgTime = line.slice(line.indexOf('>') + 1, line.indexOf('<'))
t.ok(line.match(/.*hello world$/), 'end of line matches')
t.ok(line.match(/(?!^)INFO.*/), 'includes level')
t.ok(line.indexOf('' + process.pid) > 0, 'includes pid')
t.ok(line.indexOf('' + hostname) > 0, 'includes hostname')
t.ok(Date.parse(localTime) > parseInt(msgTime, 10) - 2000, 'local iso time <-> Epoch timestamps match')
t.ok(Date.parse(localTime) < parseInt(msgTime, 10) + 2000, 'local iso time <-> Epoch timestamps match')
return line
}))
var instance = pino(prettier)
instance.info('>' + Date.now() + '< hello world')
})
test('pino pretty dateFormat flag', function (t) {
t.plan(6)
var prettier = pretty({ dateFormat: 'YYYY/MM/DDThh,mm,ss_SSSZ' })
prettier.pipe(split(function (line) {
var formatDate = line.slice(line.indexOf('[') + 1, line.indexOf(']'))
var msgTime = line.slice(line.indexOf('>') + 1, line.indexOf('<'))
var toISODate = formatDate.replace(/\//g, '-').replace(/,/g, ':').replace(/_/g, '.')
t.ok(line.match(/.*hello world$/), 'end of line matches')
t.ok(line.match(/(?!^)INFO.*/), 'includes level')
t.ok(line.indexOf('' + process.pid) > 0, 'includes pid')
t.ok(line.indexOf('' + hostname) > 0, 'includes hostname')
t.ok(Date.parse(toISODate) > parseInt(msgTime, 10) - 2000, 'custDateFormat <-> Epoch timestamps match')
t.ok(Date.parse(toISODate) < parseInt(msgTime, 10) + 2000, 'custDateFormat <-> Epoch timestamps match')
return line
}))
var instance = pino(prettier)
instance.info('>' + Date.now() + '< hello world')
})
test('pino pretty errorProps flag with certain props', function (t) {
t.plan(3)
var prettier = pretty({ errorProps: ['statusCode', 'originalStack'] })
var expectedLines = [
undefined,
' error stack',
'statusCode: 500',
'originalStack: original stack'
]
prettier.pipe(split(function (line) {
var expectedLine = expectedLines.shift()
if (expectedLine !== undefined) {
t.equal(line, expectedLine, 'prettifies the line')
}
}))
var instance = pino(prettier)
var error = new Error('error message')
error.stack = 'error stack'
error.statusCode = 500
error.originalStack = 'original stack'
instance.error(error)
})
test('pino pretty errorProps flag with "*" (print all nested props)', function (t) {
t.plan(9)
var prettier = pretty({ errorProps: ['*'] })
var expectedLines = [
undefined,
' error stack',
'statusCode: 500',
'originalStack: original stack',
'dataBaseSpecificError: {',
' erroMessage: "some database error message"',
' evenMoreSpecificStuff: {',
' "someErrorRelatedObject": "error"',
' }',
'}'
]
prettier.pipe(split(function (line) {
var expectedLine = expectedLines.shift()
if (expectedLine !== undefined) {
t.equal(line, expectedLine, 'prettifies the line')
}
}))
var instance = pino(prettier)
var error = new Error('error message')
error.stack = 'error stack'
error.statusCode = 500
error.originalStack = 'original stack'
error.dataBaseSpecificError = {
erroMessage: 'some database error message',
evenMoreSpecificStuff: {
someErrorRelatedObject: 'error'
}
}
instance.error(error)
})
test('pino pretty handles errors with a null stack', function (t) {
t.plan(6)
var prettier = pretty()
var expectedLines = [
undefined,
' message: "foo"',
' stack: null',
undefined,
' error: {',
' "message": "foo",',
' "stack": null',
' }'
]
prettier.pipe(split(function (line) {
var expectedLine = expectedLines.shift()
if (expectedLine !== undefined) {
t.equal(line, expectedLine, 'prettifies the line')
}
}))
var instance = pino(prettier)
const error = {message: 'foo', stack: null}
const objectWithError = {error: error}
instance.error(error)
instance.error(objectWithError)
})

Просмотреть файл

@ -1,26 +0,0 @@
Pino
To prettify logs, simply pipe a log file through pino:
cat log | pino
To highlight a string at a key other than 'msg', use -m <key>:
cat log | pino -m fooMessage
To convert Epoch timestamps to ISO timestamps use the -t flag
cat log | pino -t
To flip level and time/date in standard output use the -l flag
cat log | pino -l
Flags
-h | --help Display Help
-v | --version Display Version
-m <key> Highlight the message at the <key> property
-t Convert Epoch timestamps to ISO
-l Flip level and date