diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2c9cd91..f5733098 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,8 +40,14 @@ jobs: contents: read strategy: matrix: - node-version: [14, 16, 18] + node-version: [14, 16, 18, 20] os: [ubuntu-latest] + pino-version: [8.20.0, ^8.21.0, ^9.0.0] + exclude: + - node-version: 14 + pino-version: ^9.0.0 + - node-version: 16 + pino-version: ^9.0.0 steps: - name: Check out repo uses: actions/checkout@v4 @@ -62,6 +68,9 @@ jobs: - name: Install dependencies run: npm i --ignore-scripts + - name: Install pino ${{ matrix.pino-version }} + run: npm i --no-save pino@${{ matrix.pino-version }} + - name: Run tests run: npm run ci diff --git a/Readme.md b/Readme.md index d09e66c3..8fd48baa 100644 --- a/Readme.md +++ b/Readme.md @@ -240,10 +240,10 @@ The options accepted have keys corresponding to the options described in [CLI Ar colorize: colorette.isColorSupported, // --colorize colorizeObjects: true, //--colorizeObjects crlf: false, // --crlf - errorLikeObjectKeys: ['err', 'error'], // --errorLikeObjectKeys + errorLikeObjectKeys: ['err', 'error'], // --errorLikeObjectKeys (not required to match custom errorKey with pino >=8.21.0) errorProps: '', // --errorProps levelFirst: false, // --levelFirst - messageKey: 'msg', // --messageKey + messageKey: 'msg', // --messageKey (not required with pino >=8.21.0) levelKey: 'level', // --levelKey messageFormat: false, // --messageFormat timestampKey: 'time', // --timestampKey @@ -253,7 +253,7 @@ The options accepted have keys corresponding to the options described in [CLI Ar hideObject: false, // --hideObject singleLine: false, // --singleLine customColors: 'err:red,info:blue', // --customColors - customLevels: 'err:99,info:1', // --customLevels + customLevels: 'err:99,info:1', // --customLevels (not required with pino >=8.21.0) levelLabel: 'levelLabel', // --levelLabel minimumLevel: 'info', // --minimumLevel useOnlyCustomProps: true, // --useOnlyCustomProps diff --git a/index.d.ts b/index.d.ts index 07dc628d..72b2a6b4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -71,6 +71,8 @@ interface PrettyOptions_ { /** * The key in the JSON object to use as the highlighted message. * @default "msg" + * + * Not required when used with pino >= 8.21.0 */ messageKey?: string; /** @@ -122,6 +124,8 @@ interface PrettyOptions_ { /** * Define the log keys that are associated with error like objects. * @default ["err", "error"] + * + * Not required to handle custom errorKey when used with pino >= 8.21.0 */ errorLikeObjectKeys?: string[]; /** @@ -192,6 +196,8 @@ interface PrettyOptions_ { * * @example ( CSV ) customLevels: 'info:10,some_level:40' * @example ( Object ) customLevels: { info: 10, some_level: 40 } + * + * Not required when used with pino >= 8.21.0 */ customLevels?: string|object; /** diff --git a/index.js b/index.js index 3e500f8f..66a0afd5 100644 --- a/index.js +++ b/index.js @@ -131,8 +131,18 @@ function prettyFactory (options) { * @returns {Transform | (Transform & OnUnknown)} */ function build (opts = {}) { - const pretty = prettyFactory(opts) + let pretty = prettyFactory(opts) return abstractTransport(function (source) { + source.on('message', function pinoConfigListener (message) { + if (!message || message.code !== 'PINO_CONFIG') return + Object.assign(opts, { + messageKey: message.config.messageKey, + errorLikeObjectKeys: Array.from(new Set([...(opts.errorLikeObjectKeys || ERROR_LIKE_KEYS), message.config.errorKey])), + customLevels: message.config.levels.values + }) + pretty = prettyFactory(opts) + source.off('message', pinoConfigListener) + }) const stream = new Transform({ objectMode: true, autoDestroy: true, diff --git a/package.json b/package.json index 628a11e1..4587fa96 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "pino": "^8.0.0", "pre-commit": "^1.2.2", "rimraf": "^3.0.2", + "semver": "^7.6.0", "snazzy": "^9.0.0", "standard": "^17.0.0", "tap": "^16.0.0", diff --git a/test/basic.test.js b/test/basic.test.js index 1ee96c35..ba53a15c 100644 --- a/test/basic.test.js +++ b/test/basic.test.js @@ -11,6 +11,7 @@ const path = require('path') const rimraf = require('rimraf') const { join } = require('path') const fs = require('fs') +const semver = require('semver') const pinoPretty = require('..') const SonicBoom = require('sonic-boom') const _prettyFactory = pinoPretty.prettyFactory @@ -1074,7 +1075,7 @@ test('basic prettifier tests', (t) => { t.test('stream usage', async (t) => { t.plan(1) const tmpDir = path.join(__dirname, '.tmp_' + Date.now()) - t.teardown(() => rimraf(tmpDir, noop)) + t.teardown(() => rimraf.sync(tmpDir)) const destination = join(tmpDir, 'output') @@ -1102,24 +1103,28 @@ test('basic prettifier tests', (t) => { t.test('sync option', async (t) => { t.plan(1) const tmpDir = path.join(__dirname, '.tmp_' + Date.now()) - t.teardown(() => rimraf(tmpDir, noop)) + t.teardown(() => rimraf.sync(tmpDir)) const destination = join(tmpDir, 'output') - const pretty = pinoPretty({ - singleLine: true, - colorize: false, - mkdir: true, - append: false, - sync: true, - destination - }) - const log = pino(pretty) - log.info({ msg: 'message', extra: { foo: 'bar', number: 42 }, upper: 'foobar' }) + const log = pino(pino.transport({ + target: '..', + options: { + singleLine: true, + colorize: false, + mkdir: true, + append: false, + sync: true, + destination + } + })) + log.info({ msg: 'message', extra: { foo: 'bar', number: 43 }, upper: 'foobar' }) + + await watchFileCreated(destination) const formatted = fs.readFileSync(destination, 'utf8') - t.equal(formatted, `[${formattedEpoch}] INFO (${pid}): message {"extra":{"foo":"bar","number":42},"upper":"foobar"}\n`) + t.equal(formatted, `[${formattedEpoch}] INFO (${pid}): message {"extra":{"foo":"bar","number":43},"upper":"foobar"}\n`) }) t.test('support custom colors object', async (t) => { @@ -1151,6 +1156,59 @@ test('basic prettifier tests', (t) => { t.end() }) +if (semver.gte(pino.version, '8.21.0')) { + test('using pino config', (t) => { + t.beforeEach(() => { + Date.originalNow = Date.now + Date.now = () => epoch + }) + t.afterEach(() => { + Date.now = Date.originalNow + delete Date.originalNow + }) + + t.test('can use different message keys', (t) => { + t.plan(1) + const destination = new Writable({ + write (formatted, enc, cb) { + t.equal( + formatted.toString(), + `[${formattedEpoch}] INFO (${pid}): baz\n` + ) + cb() + } + }) + const pretty = pinoPretty({ + destination, + colorize: false + }) + const log = pino({ messageKey: 'bar' }, pretty) + log.info({ bar: 'baz' }) + }) + + t.test('handles customLogLevels', (t) => { + t.plan(1) + const destination = new Writable({ + write (formatted, enc, cb) { + t.equal( + formatted.toString(), + `[${formattedEpoch}] TESTCUSTOM (${pid}): test message\n` + ) + cb() + } + }) + const pretty = pinoPretty({ + destination, + colorize: false + }) + const log = pino({ customLevels: { testCustom: 35 } }, pretty) + log.testCustom('test message') + }) + + t.end() + }) +} + function watchFileCreated (filename) { return new Promise((resolve, reject) => { const TIMEOUT = 2000 @@ -1171,5 +1229,3 @@ function watchFileCreated (filename) { }, INTERVAL) }) } - -function noop () {} diff --git a/test/error-objects.test.js b/test/error-objects.test.js index e92e7ddf..b9840c6c 100644 --- a/test/error-objects.test.js +++ b/test/error-objects.test.js @@ -5,8 +5,10 @@ process.env.TZ = 'UTC' const Writable = require('stream').Writable const test = require('tap').test const pino = require('pino') +const semver = require('semver') const serializers = pino.stdSerializers -const _prettyFactory = require('../').prettyFactory +const pinoPretty = require('../') +const _prettyFactory = pinoPretty.prettyFactory function prettyFactory (opts) { if (!opts) { @@ -452,3 +454,46 @@ test('error like objects tests', (t) => { t.end() }) + +if (semver.gte(pino.version, '8.21.0')) { + test('using pino config', (t) => { + t.beforeEach(() => { + Date.originalNow = Date.now + Date.now = () => epoch + }) + t.afterEach(() => { + Date.now = Date.originalNow + delete Date.originalNow + }) + + t.test('prettifies Error in custom errorKey', (t) => { + t.plan(8) + const destination = new Writable({ + write (chunk, enc, cb) { + const formatted = chunk.toString() + const lines = formatted.split('\n') + t.equal(lines.length, expected.length + 7) + t.equal(lines[0], `[${formattedEpoch}] INFO (${pid}): hello world`) + t.match(lines[1], /\s{4}customErrorKey: {/) + 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 `` + t.match(lines[6], /\s{10}(at Test.t.test|at Test.)/) + cb() + } + }) + const pretty = pinoPretty({ + destination, + colorize: false + }) + const log = pino({ errorKey: 'customErrorKey' }, pretty) + const err = Error('hello world') + const expected = err.stack.split('\n') + log.info({ customErrorKey: err }) + }) + + t.end() + }) +}