diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5360c5a..fdd0fe19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: COVERALLS: ${{ steps.coveralls-trigger.outputs.COVERALLS_TRIGGER }} strategy: matrix: - node-version: [10, 12, 14, 16] + node-version: [12, 14, 16] os: [ubuntu-latest] steps: - name: Checkout diff --git a/Readme.md b/Readme.md index c588d568..0c995512 100644 --- a/Readme.md +++ b/Readme.md @@ -60,6 +60,7 @@ node app.js | pino-pretty feed, to the formatted log line. - `--errorProps` (`-e`): When formatting an error object, display this list of properties. The list should be a comma-separated list of properties Default: `''`. + Do not use this option if logging from pino@7. Support will be removed from future verions. - `--levelFirst` (`-l`): Display the log level name before the logged date and time. - `--errorLikeObjectKeys` (`-k`): Define the log keys that are associated with error like objects. Default: `err,error`. diff --git a/index.js b/index.js index 0958f491..fa950aad 100644 --- a/index.js +++ b/index.js @@ -137,6 +137,7 @@ function prettyFactory (options) { line += EOL } + // pino@7+ does not log this anymore if (log.type === 'Error' && log.stack) { const prettifiedErrorLog = prettifyErrorLog({ log, diff --git a/lib/utils.js b/lib/utils.js index be0e5259..09e8958a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -198,11 +198,11 @@ function prettifyErrorLog ({ // The nested object may have "logger" type keys but since they are not // at the root level of the object being processed, we want to print them. // Thus, we invoke with `excludeLoggerKeys: false`. - const prettifiedObject = prettifyObject({ input: log[key], errorLikeKeys, excludeLoggerKeys: false, eol, ident }) - result = `${result}${key}: {${eol}${prettifiedObject}}${eol}` + const prettifiedObject = prettifyObject({ input: log[key], errorLikeKeys, excludeLoggerKeys: false, eol, ident: ident + ident }) + result = `${result}${ident}${key}: {${eol}${prettifiedObject}${ident}}${eol}` continue } - result = `${result}${key}: ${log[key]}${eol}` + result = `${result}${ident}${key}: ${log[key]}${eol}` } } @@ -471,6 +471,8 @@ function prettifyError ({ keyName, lines, eol, ident }) { 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 { result += line diff --git a/package.json b/package.json index d1ea13ad..bd5fb372 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ }, "devDependencies": { "@types/node": "^16.9.2", - "pino": "^6.13.2", + "pino": "^7.0.0", "pre-commit": "^1.2.2", "rimraf": "^3.0.2", "snazzy": "^9.0.0", diff --git a/test/basic.test.js b/test/basic.test.js index ab66492e..30898d75 100644 --- a/test/basic.test.js +++ b/test/basic.test.js @@ -541,7 +541,7 @@ test('basic prettifier tests', (t) => { ' "b": {', ' "c": "d"', ' },', - ' "a": "[Circular]"', + ' "a": "[Circular ~]"', ' }' ] const pretty = prettyFactory() diff --git a/test/cli-rc.test.js b/test/cli-rc.test.js index 2b9be2ef..393bf7fe 100644 --- a/test/cli-rc.test.js +++ b/test/cli-rc.test.js @@ -147,7 +147,7 @@ test('cli', (t) => { // Validate that the time has been translated and correct message key has been used child.on('error', t.threw) child.stdout.on('data', (data) => { - t.equal(data.toString(), '[1594416696006] FATAL: There was an error starting the process.\n QueryError: Error during sql query: syntax error at or near SELECTT\n at /home/me/projects/example/sql.js\n at /home/me/projects/example/index.js\nquerySql: SELECTT * FROM "test" WHERE id = $1;\nqueryArgs: 12\n') + t.equal(data.toString(), '[1594416696006] FATAL: There was an error starting the process.\n QueryError: Error during sql query: syntax error at or near SELECTT\n at /home/me/projects/example/sql.js\n at /home/me/projects/example/index.js\n querySql: SELECTT * FROM "test" WHERE id = $1;\n queryArgs: 12\n') }) child.stdin.write('{"level":60,"time":1594416696006,"msg":"There was an error starting the process.","type":"Error","stack":"QueryError: Error during sql query: syntax error at or near SELECTT\\n at /home/me/projects/example/sql.js\\n at /home/me/projects/example/index.js","querySql":"SELECTT * FROM \\"test\\" WHERE id = $1;","queryArgs":[12]}\n') t.teardown(() => { diff --git a/test/error-objects.test.js b/test/error-objects.test.js index d91a6e89..5e7b345b 100644 --- a/test/error-objects.test.js +++ b/test/error-objects.test.js @@ -21,7 +21,7 @@ const epoch = 1522431328992 const pid = process.pid const hostname = os.hostname() -test('error like objects tests', (t) => { +test('error like objects tests', { only: true }, (t) => { t.beforeEach(() => { Date.originalNow = Date.now Date.now = () => epoch @@ -42,7 +42,7 @@ test('error like objects tests', (t) => { write (chunk, enc, cb) { const formatted = pretty(chunk.toString()) const lines = formatted.split('\n') - t.equal(lines.length, expected.length + 1) + t.equal(lines.length, expected.length + 6) t.equal(lines[0], `[${epoch}] INFO (${pid} on ${hostname}): hello world`) cb() } @@ -58,8 +58,8 @@ test('error like objects tests', (t) => { write (chunk, enc, cb) { const formatted = pretty(chunk.toString()) t.match(formatted, /\s{4}error stack/) - t.match(formatted, /statusCode: 500/) - t.match(formatted, /originalStack: original stack/) + t.match(formatted, /"statusCode": 500/) + t.match(formatted, /"originalStack": "original stack"/) cb() } })) @@ -94,7 +94,7 @@ test('error like objects tests', (t) => { const formatted = pretty(chunk.toString()) const lines = formatted.split('\n') t.equal(lines.length, expected.length + 6) - t.equal(lines[0], `[${epoch}] INFO (${pid} on ${hostname}):`) + t.equal(lines[0], `[${epoch}] INFO (${pid} on ${hostname}): hello world`) t.match(lines[1], /\s{4}err: {/) t.match(lines[2], /\s{6}"type": "Error",/) t.match(lines[3], /\s{6}"message": "hello world",/) @@ -129,7 +129,7 @@ test('error like objects tests', (t) => { const formatted = pretty(chunk.toString()) const lines = formatted.split('\n') t.equal(lines.length, expected.length + 5) - t.equal(lines[0], `[${epoch}] INFO (${pid} on ${hostname}): {"extra":{"a":1,"b":2}}`) + t.equal(lines[0], `[${epoch}] INFO (${pid} on ${hostname}): hello world {"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",/) @@ -145,7 +145,7 @@ test('error like objects tests', (t) => { }) t.test('prettifies Error in property within errorLikeObjectKeys with custom function', (t) => { - t.plan(1) + t.plan(4) const pretty = prettyFactory({ errorLikeObjectKeys: ['err'], customPrettifiers: { @@ -161,7 +161,12 @@ test('error like objects tests', (t) => { const log = pino({ serializers: { err: serializers.err } }, new Writable({ write (chunk, enc, cb) { const formatted = pretty(chunk.toString()) - t.equal(formatted, `[${epoch}] INFO (${pid} on ${hostname}):\n err: error is hello world\n`) + const lines = formatted.split('\n') + t.equal(lines.length, 3) + t.equal(lines[0], `[${epoch}] INFO (${pid} on ${hostname}): hello world`) + t.equal(lines[1], ' err: error is hello world') + t.equal(lines[2], '') + cb() } })) @@ -185,7 +190,7 @@ test('error like objects tests', (t) => { const formatted = pretty(chunk.toString()) const lines = formatted.split('\n') t.equal(lines.length, expected.length + 6) - t.equal(lines[0], `[${epoch}] INFO (${pid} on ${hostname}):`) + t.equal(lines[0], `[${epoch}] INFO (${pid} on ${hostname}): hello world`) t.match(lines[1], /\s{4}err: {$/) t.match(lines[2], /\s{6}"type": "Error",$/) t.match(lines[3], /\s{6}"message": "hello world",$/) @@ -215,7 +220,7 @@ test('error like objects tests', (t) => { const formatted = pretty(chunk.toString()) const lines = formatted.split('\n') t.equal(lines.length, expected.length + 7) - t.equal(lines[0], `[${epoch}] INFO (${pid} on ${hostname}):`) + t.equal(lines[0], `[${epoch}] INFO (${pid} on ${hostname}): hello world`) t.match(lines[1], /\s{4}err: {/) t.match(lines[2], /\s{6}"type": "Error",/) t.match(lines[3], /\s{6}"message": "hello world",/) @@ -232,19 +237,24 @@ test('error like objects tests', (t) => { }) t.test('errorProps flag with "*" (print all nested props)', function (t) { - t.plan(9) const pretty = prettyFactory({ errorProps: '*' }) const expectedLines = [ - ' error stack', - 'statusCode: 500', - 'originalStack: original stack', - 'dataBaseSpecificError: {', - ' erroMessage: "some database error message"', - ' evenMoreSpecificStuff: {', - ' "someErrorRelatedObject": "error"', - ' }', - '}' + ' err: {', + ' "type": "Error",', + ' "message": "error message",', + ' "stack":', + ' error stack', + ' "statusCode": 500,', + ' "originalStack": "original stack",', + ' "dataBaseSpecificError": {', + ' "erroMessage": "some database error message",', + ' "evenMoreSpecificStuff": {', + ' "someErrorRelatedObject": "error"', + ' }', + ' }', + ' }' ] + t.plan(expectedLines.length) const log = pino({}, new Writable({ write (chunk, enc, cb) { const formatted = pretty(chunk.toString()) @@ -271,6 +281,107 @@ test('error like objects tests', (t) => { log.error(error) }) + t.test('errorProps: legacy error object at top level', { only: true }, function (t) { + const pretty = prettyFactory({ errorProps: '*' }) + const expectedLines = [ + 'INFO:', + ' error stack', + ' message: hello message', + ' statusCode: 500', + ' originalStack: original stack', + ' dataBaseSpecificError: {', + ' erroMessage: "some database error message"', + ' evenMoreSpecificStuff: {', + ' "someErrorRelatedObject": "error"', + ' }', + ' }', + '' + ] + + t.plan(expectedLines.length) + + const error = {} + error.level = 30 + error.message = 'hello message' + error.type = 'Error' + error.stack = 'error stack' + error.statusCode = 500 + error.originalStack = 'original stack' + error.dataBaseSpecificError = { + erroMessage: 'some database error message', + evenMoreSpecificStuff: { + someErrorRelatedObject: 'error' + } + } + + const formatted = pretty(JSON.stringify(error)) + const lines = formatted.split('\n') + for (let i = 0; i < lines.length; i += 1) { + t.equal(lines[i], expectedLines[i]) + } + }) + + t.test('errorProps flag with a single property', function (t) { + const pretty = prettyFactory({ errorProps: 'originalStack' }) + const expectedLines = [ + 'INFO:', + ' error stack', + ' originalStack: original stack', + '' + ] + t.plan(expectedLines.length) + + const error = {} + error.level = 30 + error.message = 'hello message' + error.type = 'Error' + error.stack = 'error stack' + error.statusCode = 500 + error.originalStack = 'original stack' + error.dataBaseSpecificError = { + erroMessage: 'some database error message', + evenMoreSpecificStuff: { + someErrorRelatedObject: 'error' + } + } + + const formatted = pretty(JSON.stringify(error)) + const lines = formatted.split('\n') + for (let i = 0; i < lines.length; i += 1) { + t.equal(lines[i], expectedLines[i]) + } + }) + + t.test('errorProps flag with a single property non existent', function (t) { + const pretty = prettyFactory({ errorProps: 'originalStackABC' }) + const expectedLines = [ + 'INFO:', + ' error stack', + '' + ] + t.plan(expectedLines.length) + + const error = {} + error.level = 30 + error.message = 'hello message' + error.type = 'Error' + error.stack = 'error stack' + error.statusCode = 500 + error.originalStack = 'original stack' + error.dataBaseSpecificError = { + erroMessage: 'some database error message', + evenMoreSpecificStuff: { + someErrorRelatedObject: 'error' + } + } + + const formatted = pretty(JSON.stringify(error)) + const lines = formatted.split('\n') + for (let i = 0; i < lines.length; i += 1) { + t.equal(lines[i], expectedLines[i]) + } + }) + t.test('handles errors with a null stack', (t) => { t.plan(2) const pretty = prettyFactory() @@ -288,20 +399,21 @@ test('error like objects tests', (t) => { }) t.test('handles errors with a null stack for Error object', (t) => { - t.plan(3) const pretty = prettyFactory() const expectedLines = [ - ' some: "property"', - ' stack: null', - ' type: "Error"' + '"type": "Error"', + '"message": "error message"', + '"stack": null', + '"some": "property"' ] + t.plan(expectedLines.length) const log = pino({}, new Writable({ write (chunk, enc, cb) { const formatted = pretty(chunk.toString()) const lines = formatted.split('\n') - lines.shift(); lines.pop() + lines.shift(); lines.shift(); lines.pop(); lines.pop() for (let i = 0; i < lines.length; i += 1) { - t.ok(expectedLines.includes(lines[i])) + t.ok(lines[i].includes(expectedLines[i])) } cb() }