pino-pretty
Advanced tools
Comparing version 4.5.0 to 4.6.0
@@ -49,2 +49,3 @@ #!/usr/bin/env node | ||
.option(['H', 'hideObject'], 'Hide objects from output (but not error object)') | ||
.option(['S', 'singleLine'], 'Print all non-error objects on a single line') | ||
.option('config', 'specify a path to a json file containing the pino-pretty options') | ||
@@ -51,0 +52,0 @@ |
@@ -39,3 +39,4 @@ 'use strict' | ||
customPrettifiers: {}, | ||
hideObject: false | ||
hideObject: false, | ||
singleLine: false | ||
} | ||
@@ -57,2 +58,3 @@ | ||
const hideObject = opts.hideObject | ||
const singleLine = opts.singleLine | ||
@@ -136,3 +138,3 @@ const colorizer = colors(opts.colorize) | ||
if (line.length > 0) { | ||
line += EOL | ||
line += (singleLine ? ' ' : EOL) | ||
} | ||
@@ -157,3 +159,4 @@ | ||
eol: EOL, | ||
ident: IDENT | ||
ident: IDENT, | ||
singleLine | ||
}) | ||
@@ -160,0 +163,0 @@ line += prettifiedObject |
@@ -54,3 +54,3 @@ 'use strict' | ||
* | ||
* @param {bool} [useColors=false] When `true` a function that applies standard | ||
* @param {boolean} [useColors=false] When `true` a function that applies standard | ||
* terminal colors is returned. | ||
@@ -57,0 +57,0 @@ * |
129
lib/utils.js
@@ -29,3 +29,4 @@ 'use strict' | ||
formatTime, | ||
joinLinesWithIndentation | ||
joinLinesWithIndentation, | ||
prettifyError | ||
} | ||
@@ -38,3 +39,3 @@ | ||
* valid for `new Date()`. | ||
* @param {bool|string} [translateTime=false] When `false`, the given `epoch` | ||
* @param {boolean|string} [translateTime=false] When `false`, the given `epoch` | ||
* will simply be returned. When `true`, the given `epoch` will be converted | ||
@@ -102,3 +103,3 @@ * to a string at UTC using the `DATE_FORMAT` constant. If `translateTime` is | ||
const lines = input.split(/\r?\n/) | ||
for (var i = 1; i < lines.length; i += 1) { | ||
for (let i = 1; i < lines.length; i += 1) { | ||
lines[i] = ident + lines[i] | ||
@@ -152,3 +153,3 @@ } | ||
for (var i = 0; i < propertiesToPrint.length; i += 1) { | ||
for (let i = 0; i < propertiesToPrint.length; i += 1) { | ||
const key = propertiesToPrint[i] | ||
@@ -298,2 +299,5 @@ if (key in log === false) continue | ||
* keys should be excluded from prettification. Default: `true`. | ||
* @param {boolean} [input.singleLine] Should non-error keys all be formatted | ||
* on a single line? This does NOT apply to errors, which will still be | ||
* multi-line. Default: `false` | ||
* | ||
@@ -310,5 +314,5 @@ * @returns {string} The prettified string. This can be as little as `''` if | ||
errorLikeKeys = ERROR_LIKE_KEYS, | ||
excludeLoggerKeys = true | ||
excludeLoggerKeys = true, | ||
singleLine = false | ||
}) { | ||
const objectKeys = Object.keys(input) | ||
const keysToIgnore = [].concat(skipKeys) | ||
@@ -320,43 +324,51 @@ | ||
const keysToIterate = objectKeys.filter(k => keysToIgnore.includes(k) === false) | ||
for (var i = 0; i < objectKeys.length; i += 1) { | ||
const keyName = keysToIterate[i] | ||
const keyValue = input[keyName] | ||
// Split object keys into two categories: error and non-error | ||
const { plain, errors } = Object.entries(input).reduce(({ plain, errors }, [k, v]) => { | ||
if (keysToIgnore.includes(k) === false) { | ||
// Pre-apply custom prettifiers, because all 3 cases below will need this | ||
const pretty = typeof customPrettifiers[k] === 'function' | ||
? customPrettifiers[k](v, k, input) | ||
: v | ||
if (errorLikeKeys.includes(k)) { | ||
errors[k] = pretty | ||
} else { | ||
plain[k] = pretty | ||
} | ||
} | ||
return { plain, errors } | ||
}, { plain: {}, errors: {} }) | ||
if (keyValue === undefined) continue | ||
let lines | ||
if (typeof customPrettifiers[keyName] === 'function') { | ||
lines = customPrettifiers[keyName](keyValue, keyName, input) | ||
} else { | ||
lines = stringifySafe(keyValue, null, 2) | ||
if (singleLine) { | ||
// Stringify the entire object as a single JSON line | ||
if (Object.keys(plain).length > 0) { | ||
result += stringifySafe(plain) | ||
} | ||
result += eol | ||
} else { | ||
// Put each object entry on its own line | ||
Object.entries(plain).forEach(([keyName, keyValue]) => { | ||
// custom prettifiers are already applied above, so we can skip it now | ||
const lines = typeof customPrettifiers[keyName] === 'function' | ||
? keyValue | ||
: stringifySafe(keyValue, null, 2) | ||
if (lines === undefined) continue | ||
const joinedLines = joinLinesWithIndentation({ input: lines, ident, eol }) | ||
if (lines === undefined) return | ||
if (errorLikeKeys.includes(keyName) === true) { | ||
const splitLines = `${ident}${keyName}: ${joinedLines}${eol}`.split(eol) | ||
for (var j = 0; j < splitLines.length; j += 1) { | ||
if (j !== 0) result += eol | ||
const line = splitLines[j] | ||
if (/^\s*"stack"/.test(line)) { | ||
const matches = /^(\s*"stack":)\s*(".*"),?$/.exec(line) | ||
/* istanbul ignore else */ | ||
if (matches && matches.length === 3) { | ||
const indentSize = /^\s*/.exec(line)[0].length + 4 | ||
const indentation = ' '.repeat(indentSize) | ||
const stackMessage = matches[2] | ||
result += matches[1] + eol + indentation + JSON.parse(stackMessage).replace(/\n/g, eol + indentation) | ||
} | ||
} else { | ||
result += line | ||
} | ||
} | ||
} else { | ||
const joinedLines = joinLinesWithIndentation({ input: lines, ident, eol }) | ||
result += `${ident}${keyName}: ${joinedLines}${eol}` | ||
} | ||
}) | ||
} | ||
// Errors | ||
Object.entries(errors).forEach(([keyName, keyValue]) => { | ||
// custom prettifiers are already applied above, so we can skip it now | ||
const lines = typeof customPrettifiers[keyName] === 'function' | ||
? keyValue | ||
: stringifySafe(keyValue, null, 2) | ||
if (lines === undefined) return | ||
result += prettifyError({ keyName, lines, eol, ident }) | ||
}) | ||
return result | ||
@@ -372,3 +384,3 @@ } | ||
* @param {string} [input.timestampKey='time'] The log property that should be used to resolve timestamp value | ||
* @param {bool|string} [input.translateFormat=undefined] When `true` the | ||
* @param {boolean|string} [input.translateFormat=undefined] When `true` the | ||
* timestamp will be prettified into a string at UTC using the default | ||
@@ -398,1 +410,36 @@ * `DATE_FORMAT`. If a string, then `translateFormat` will be used as the format | ||
} | ||
/** | ||
* Prettifies an error string into a multi-line format. | ||
* @param {object} input | ||
* @param {string} input.keyName The key assigned to this error in the log object | ||
* @param {string} input.lines The STRINGIFIED error. If the error field has a | ||
* custom prettifier, that should be pre-applied as well | ||
* @param {string} input.ident The indentation sequence to use | ||
* @param {string} input.eol The EOL sequence to use | ||
*/ | ||
function prettifyError ({ keyName, lines, eol, ident }) { | ||
let result = '' | ||
const joinedLines = joinLinesWithIndentation({ input: lines, ident, eol }) | ||
const splitLines = `${ident}${keyName}: ${joinedLines}${eol}`.split(eol) | ||
for (let j = 0; j < splitLines.length; j += 1) { | ||
if (j !== 0) result += eol | ||
const line = splitLines[j] | ||
if (/^\s*"stack"/.test(line)) { | ||
const matches = /^(\s*"stack":)\s*(".*"),?$/.exec(line) | ||
/* istanbul ignore else */ | ||
if (matches && matches.length === 3) { | ||
const indentSize = /^\s*/.exec(line)[0].length + 4 | ||
const indentation = ' '.repeat(indentSize) | ||
const stackMessage = matches[2] | ||
result += matches[1] + eol + indentation + JSON.parse(stackMessage).replace(/\n/g, eol + indentation) | ||
} | ||
} else { | ||
result += line | ||
} | ||
} | ||
return result | ||
} |
{ | ||
"name": "pino-pretty", | ||
"version": "4.5.0", | ||
"version": "4.6.0", | ||
"description": "Prettifier for Pino log lines", | ||
@@ -10,5 +10,5 @@ "main": "index.js", | ||
"scripts": { | ||
"ci": "standard && tap 'test/**/*.test.js' --coverage-report=lcovonly", | ||
"lint": "standard | snazzy", | ||
"test": "tap --100 'test/**/*.test.js'", | ||
"ci": "standard && tap --100 'test/**/*.test.js'" | ||
"test": "tap --100 'test/**/*.test.js'" | ||
}, | ||
@@ -36,3 +36,3 @@ "repository": { | ||
"chalk": "^4.0.0", | ||
"dateformat": "^3.0.3", | ||
"dateformat": "^4.5.1", | ||
"fast-safe-stringify": "^2.0.7", | ||
@@ -50,6 +50,6 @@ "jmespath": "^0.15.0", | ||
"rimraf": "^3.0.2", | ||
"snazzy": "^8.0.0", | ||
"standard": "^14.3.3", | ||
"snazzy": "^9.0.0", | ||
"standard": "^16.0.3", | ||
"tap": "^14.10.7" | ||
} | ||
} |
@@ -6,3 +6,5 @@ <a id="intro"></a> | ||
[![Build Status](https://img.shields.io/github/workflow/status/pinojs/pino-pretty/CI)](https://github.com/pinojs/pino-pretty/actions?query=workflow%3ACI) | ||
[![Coverage Status](https://img.shields.io/coveralls/github/pinojs/pino-pretty)](https://codecov.io/gh/pinojs/pino-pretty) | ||
[![Known Vulnerabilities](https://snyk.io/test/github/pinojs/pino-pretty/badge.svg)](https://snyk.io/test/github/pinojs/pino-pretty) | ||
[![Coverage Status](https://img.shields.io/coveralls/github/pinojs/pino-pretty)](https://coveralls.io/github/pinojs/pino-pretty?branch=master) | ||
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/) | ||
@@ -87,2 +89,3 @@ This module provides a basic [ndjson](http://ndjson.org/) formatter. If an | ||
- `--hideObject` (`-H`): Hide objects from output (but not error object) | ||
- `--singleLine` (`-S`): Print each log message on a single line (errors will still be multi-line) | ||
- `--config`: Specify a path to a config file containing the pino-pretty options. pino-pretty will attempt to read from a `.pino-prettyrc` in your current directory (`process.cwd`) if not specified | ||
@@ -145,4 +148,5 @@ | ||
search: 'foo == `bar`', // --search | ||
ignore: 'pid,hostname', // --ignore, | ||
hideObject: false // --hideObject | ||
ignore: 'pid,hostname', // --ignore | ||
hideObject: false, // --hideObject | ||
singleLine: false, // --singleLine | ||
customPrettifiers: {} | ||
@@ -149,0 +153,0 @@ } |
@@ -332,3 +332,3 @@ 'use strict' | ||
lines.shift(); lines.pop() | ||
for (var i = 0; i < lines.length; i += 1) { | ||
for (let i = 0; i < lines.length; i += 1) { | ||
t.is(lines[i], expectedLines[i]) | ||
@@ -517,3 +517,3 @@ } | ||
lines.shift(); lines.pop() | ||
for (var i = 0; i < lines.length; i += 1) { | ||
for (let i = 0; i < lines.length; i += 1) { | ||
t.is(lines[i], expectedLines[i]) | ||
@@ -544,3 +544,3 @@ } | ||
lines.shift(); lines.pop() | ||
for (var i = 0; i < lines.length; i += 1) { | ||
for (let i = 0; i < lines.length; i += 1) { | ||
t.is(lines[i], expectedLines[i]) | ||
@@ -703,3 +703,37 @@ } | ||
t.test('Prints extra objects on one line with singleLine=true', (t) => { | ||
t.plan(1) | ||
const pretty = prettyFactory({ | ||
singleLine: true, | ||
colorize: false, | ||
customPrettifiers: { | ||
upper: val => val.toUpperCase(), | ||
undef: () => undefined | ||
} | ||
}) | ||
const log = pino({}, new Writable({ | ||
write (chunk, enc, cb) { | ||
const formatted = pretty(chunk.toString()) | ||
t.is(formatted, `[${epoch}] INFO (${pid} on ${hostname}): message {"extra":{"foo":"bar","number":42},"upper":"FOOBAR"}\n`) | ||
cb() | ||
} | ||
})) | ||
log.info({ msg: 'message', extra: { foo: 'bar', number: 42 }, upper: 'foobar', undef: 'this will not show up' }) | ||
}) | ||
t.test('Does not print empty object with singleLine=true', (t) => { | ||
t.plan(1) | ||
const pretty = prettyFactory({ singleLine: true, colorize: false }) | ||
const log = pino({}, new Writable({ | ||
write (chunk, enc, cb) { | ||
const formatted = pretty(chunk.toString()) | ||
t.is(formatted, `[${epoch}] INFO (${pid} on ${hostname}): message \n`) | ||
cb() | ||
} | ||
})) | ||
log.info({ msg: 'message' }) | ||
}) | ||
t.end() | ||
}) |
@@ -116,3 +116,23 @@ 'use strict' | ||
t.test('singleLine=true', (t) => { | ||
t.plan(1) | ||
const logLineWithExtra = JSON.stringify(Object.assign(JSON.parse(logLine), { | ||
extra: { | ||
foo: 'bar', | ||
number: 42 | ||
} | ||
})) + '\n' | ||
const env = { TERM: 'dumb' } | ||
const child = spawn(process.argv[0], [bin, '--singleLine'], { env }) | ||
child.on('error', t.threw) | ||
child.stdout.on('data', (data) => { | ||
t.is(data.toString(), `[${epoch}] INFO (42 on foo): hello world {"extra":{"foo":"bar","number":42}}\n`) | ||
}) | ||
child.stdin.write(logLineWithExtra) | ||
t.tearDown(() => child.kill()) | ||
}) | ||
t.end() | ||
}) |
@@ -116,2 +116,62 @@ 'use strict' | ||
t.test('prettifies Error in property with singleLine=true', (t) => { | ||
// singleLine=true doesn't apply to errors | ||
t.plan(8) | ||
const pretty = prettyFactory({ | ||
singleLine: true, | ||
errorLikeObjectKeys: ['err'] | ||
}) | ||
const err = Error('hello world') | ||
const expected = [ | ||
'{"extra":{"a":1,"b":2}}', | ||
err.message, | ||
...err.stack.split('\n') | ||
] | ||
const log = pino({ serializers: { err: serializers.err } }, new Writable({ | ||
write (chunk, enc, cb) { | ||
const formatted = pretty(chunk.toString()) | ||
const lines = formatted.split('\n') | ||
t.is(lines.length, expected.length + 5) | ||
t.is(lines[0], `[${epoch}] INFO (${pid} on ${hostname}): {"extra":{"a":1,"b":2}}`) | ||
t.match(lines[1], /\s{4}err: {/) | ||
t.match(lines[2], /\s{6}"type": "Error",/) | ||
t.match(lines[3], /\s{6}"message": "hello world",/) | ||
t.match(lines[4], /\s{6}"stack":/) | ||
t.match(lines[5], /\s{6}Error: hello world/) | ||
// Node 12 labels the test `<anonymous>` | ||
t.match(lines[6], /\s{10}(at Test.t.test|at Test.<anonymous>)/) | ||
cb() | ||
} | ||
})) | ||
log.info({ err, extra: { a: 1, b: 2 } }) | ||
}) | ||
t.test('prettifies Error in property within errorLikeObjectKeys with custom function', (t) => { | ||
t.plan(1) | ||
const pretty = prettyFactory({ | ||
errorLikeObjectKeys: ['err'], | ||
customPrettifiers: { | ||
err: val => `error is ${val.message}` | ||
} | ||
}) | ||
const err = Error('hello world') | ||
err.stack = 'Error: hello world\n at anonymous (C:\\project\\node_modules\\example\\index.js)' | ||
const expected = err.stack.split('\n') | ||
expected.unshift(err.message) | ||
const log = pino({ serializers: { err: serializers.err } }, new Writable({ | ||
write (chunk, enc, cb) { | ||
const formatted = pretty(chunk.toString()) | ||
t.is(formatted, `[${epoch}] INFO (${pid} on ${hostname}):\n err: error is hello world\n`) | ||
cb() | ||
} | ||
})) | ||
log.info({ err }) | ||
}) | ||
t.test('prettifies Error in property within errorLikeObjectKeys when stack has escaped characters', (t) => { | ||
@@ -198,3 +258,3 @@ t.plan(8) | ||
lines.shift(); lines.pop() | ||
for (var i = 0; i < lines.length; i += 1) { | ||
for (let i = 0; i < lines.length; i += 1) { | ||
t.is(lines[i], expectedLines[i]) | ||
@@ -249,3 +309,3 @@ } | ||
lines.shift(); lines.pop() | ||
for (var i = 0; i < lines.length; i += 1) { | ||
for (let i = 0; i < lines.length; i += 1) { | ||
t.true(expectedLines.includes(lines[i])) | ||
@@ -252,0 +312,0 @@ } |
'use strict' | ||
const tap = require('tap') | ||
const stringifySafe = require('fast-safe-stringify') | ||
const { internals } = require('../../lib/utils') | ||
@@ -84,1 +85,14 @@ | ||
}) | ||
tap.test('#prettifyError', t => { | ||
t.test('prettifies error', t => { | ||
const error = Error('Bad error!') | ||
const lines = stringifySafe(error, Object.getOwnPropertyNames(error), 2) | ||
const prettyError = internals.prettifyError({ keyName: 'errorKey', lines, ident: ' ', eol: '\n' }) | ||
t.match(prettyError, /\s*errorKey: {\n\s*"stack":[\s\S]*"message": "Bad error!"/) | ||
t.end() | ||
}) | ||
t.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
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
279625
22
2418
193
+ Addeddateformat@4.6.3(transitive)
- Removeddateformat@3.0.3(transitive)
Updateddateformat@^4.5.1