pino
Version:
super fast, all natural json logger
875 lines (783 loc) • 23.2 kB
JavaScript
'use strict'
const os = require('node:os')
const { readFileSync } = require('node:fs')
const { test } = require('tap')
const { sink, check, once, watchFileCreated, file } = require('./helper')
const pino = require('../')
const { version } = require('../package.json')
const { pid } = process
const hostname = os.hostname()
test('pino version is exposed on export', async ({ equal }) => {
equal(pino.version, version)
})
test('pino version is exposed on instance', async ({ equal }) => {
const instance = pino()
equal(instance.version, version)
})
test('child instance exposes pino version', async ({ equal }) => {
const child = pino().child({ foo: 'bar' })
equal(child.version, version)
})
test('bindings are exposed on every instance', async ({ same }) => {
const instance = pino()
same(instance.bindings(), {})
})
test('bindings contain the name and the child bindings', async ({ same }) => {
const instance = pino({ name: 'basicTest', level: 'info' }).child({ foo: 'bar' }).child({ a: 2 })
same(instance.bindings(), { name: 'basicTest', foo: 'bar', a: 2 })
})
test('set bindings on instance', async ({ same }) => {
const instance = pino({ name: 'basicTest', level: 'info' })
instance.setBindings({ foo: 'bar' })
same(instance.bindings(), { name: 'basicTest', foo: 'bar' })
})
test('newly set bindings overwrite old bindings', async ({ same }) => {
const instance = pino({ name: 'basicTest', level: 'info', base: { foo: 'bar' } })
instance.setBindings({ foo: 'baz' })
same(instance.bindings(), { name: 'basicTest', foo: 'baz' })
})
test('set bindings on child instance', async ({ same }) => {
const child = pino({ name: 'basicTest', level: 'info' }).child({})
child.setBindings({ foo: 'bar' })
same(child.bindings(), { name: 'basicTest', foo: 'bar' })
})
test('child should have bindings set by parent', async ({ same }) => {
const instance = pino({ name: 'basicTest', level: 'info' })
instance.setBindings({ foo: 'bar' })
const child = instance.child({})
same(child.bindings(), { name: 'basicTest', foo: 'bar' })
})
test('child should not share bindings of parent set after child creation', async ({ same }) => {
const instance = pino({ name: 'basicTest', level: 'info' })
const child = instance.child({})
instance.setBindings({ foo: 'bar' })
same(instance.bindings(), { name: 'basicTest', foo: 'bar' })
same(child.bindings(), { name: 'basicTest' })
})
function levelTest (name, level) {
test(`${name} logs as ${level}`, async ({ equal }) => {
const stream = sink()
const instance = pino(stream)
instance.level = name
instance[name]('hello world')
check(equal, await once(stream, 'data'), level, 'hello world')
})
test(`passing objects at level ${name}`, async ({ equal, same }) => {
const stream = sink()
const instance = pino(stream)
instance.level = name
const obj = { hello: 'world' }
instance[name](obj)
const result = await once(stream, 'data')
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
equal(result.pid, pid)
equal(result.hostname, hostname)
equal(result.level, level)
equal(result.hello, 'world')
same(Object.keys(obj), ['hello'])
})
test(`passing an object and a string at level ${name}`, async ({ equal, same }) => {
const stream = sink()
const instance = pino(stream)
instance.level = name
const obj = { hello: 'world' }
instance[name](obj, 'a string')
const result = await once(stream, 'data')
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
delete result.time
same(result, {
pid,
hostname,
level,
msg: 'a string',
hello: 'world'
})
same(Object.keys(obj), ['hello'])
})
test(`passing a undefined and a string at level ${name}`, async ({ equal, same }) => {
const stream = sink()
const instance = pino(stream)
instance.level = name
instance[name](undefined, 'a string')
const result = await once(stream, 'data')
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
delete result.time
same(result, {
pid,
hostname,
level,
msg: 'a string'
})
})
test(`overriding object key by string at level ${name}`, async ({ equal, same }) => {
const stream = sink()
const instance = pino(stream)
instance.level = name
instance[name]({ hello: 'world', msg: 'object' }, 'string')
const result = await once(stream, 'data')
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
delete result.time
same(result, {
pid,
hostname,
level,
msg: 'string',
hello: 'world'
})
})
test(`formatting logs as ${name}`, async ({ equal }) => {
const stream = sink()
const instance = pino(stream)
instance.level = name
instance[name]('hello %d', 42)
const result = await once(stream, 'data')
check(equal, result, level, 'hello 42')
})
test(`formatting a symbol at level ${name}`, async ({ equal }) => {
const stream = sink()
const instance = pino(stream)
instance.level = name
const sym = Symbol('foo')
instance[name]('hello %s', sym)
const result = await once(stream, 'data')
check(equal, result, level, 'hello Symbol(foo)')
})
test(`passing error with a serializer at level ${name}`, async ({ equal, same }) => {
const stream = sink()
const err = new Error('myerror')
const instance = pino({
serializers: {
err: pino.stdSerializers.err
}
}, stream)
instance.level = name
instance[name]({ err })
const result = await once(stream, 'data')
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
delete result.time
same(result, {
pid,
hostname,
level,
err: {
type: 'Error',
message: err.message,
stack: err.stack
},
msg: err.message
})
})
test(`child logger for level ${name}`, async ({ equal, same }) => {
const stream = sink()
const instance = pino(stream)
instance.level = name
const child = instance.child({ hello: 'world' })
child[name]('hello world')
const result = await once(stream, 'data')
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
delete result.time
same(result, {
pid,
hostname,
level,
msg: 'hello world',
hello: 'world'
})
})
}
levelTest('fatal', 60)
levelTest('error', 50)
levelTest('warn', 40)
levelTest('info', 30)
levelTest('debug', 20)
levelTest('trace', 10)
test('serializers can return undefined to strip field', async ({ equal }) => {
const stream = sink()
const instance = pino({
serializers: {
test () { return undefined }
}
}, stream)
instance.info({ test: 'sensitive info' })
const result = await once(stream, 'data')
equal('test' in result, false)
})
test('streams receive a message event with PINO_CONFIG', ({ match, end }) => {
const stream = sink()
stream.once('message', (message) => {
match(message, {
code: 'PINO_CONFIG',
config: {
errorKey: 'err',
levels: {
labels: {
10: 'trace',
20: 'debug',
30: 'info',
40: 'warn',
50: 'error',
60: 'fatal'
},
values: {
debug: 20,
error: 50,
fatal: 60,
info: 30,
trace: 10,
warn: 40
}
},
messageKey: 'msg'
}
})
end()
})
pino(stream)
})
test('does not explode with a circular ref', async ({ doesNotThrow }) => {
const stream = sink()
const instance = pino(stream)
const b = {}
const a = {
hello: b
}
b.a = a // circular ref
doesNotThrow(() => instance.info(a))
})
test('set the name', async ({ equal, same }) => {
const stream = sink()
const instance = pino({
name: 'hello'
}, stream)
instance.fatal('this is fatal')
const result = await once(stream, 'data')
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
delete result.time
same(result, {
pid,
hostname,
level: 60,
name: 'hello',
msg: 'this is fatal'
})
})
test('set the messageKey', async ({ equal, same }) => {
const stream = sink()
const message = 'hello world'
const messageKey = 'fooMessage'
const instance = pino({
messageKey
}, stream)
instance.info(message)
const result = await once(stream, 'data')
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
delete result.time
same(result, {
pid,
hostname,
level: 30,
fooMessage: message
})
})
test('set the nestedKey', async ({ equal, same }) => {
const stream = sink()
const object = { hello: 'world' }
const nestedKey = 'stuff'
const instance = pino({
nestedKey
}, stream)
instance.info(object)
const result = await once(stream, 'data')
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
delete result.time
same(result, {
pid,
hostname,
level: 30,
stuff: object
})
})
test('set undefined properties', async ({ equal, same }) => {
const stream = sink()
const instance = pino(stream)
instance.info({ hello: 'world', property: undefined })
const result = await once(stream, 'data')
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
delete result.time
same(result, {
pid,
hostname,
level: 30,
hello: 'world'
})
})
test('prototype properties are not logged', async ({ equal }) => {
const stream = sink()
const instance = pino(stream)
instance.info(Object.create({ hello: 'world' }))
const { hello } = await once(stream, 'data')
equal(hello, undefined)
})
test('set the base', async ({ equal, same }) => {
const stream = sink()
const instance = pino({
base: {
a: 'b'
}
}, stream)
instance.fatal('this is fatal')
const result = await once(stream, 'data')
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
delete result.time
same(result, {
a: 'b',
level: 60,
msg: 'this is fatal'
})
})
test('set the base to null', async ({ equal, same }) => {
const stream = sink()
const instance = pino({
base: null
}, stream)
instance.fatal('this is fatal')
const result = await once(stream, 'data')
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
delete result.time
same(result, {
level: 60,
msg: 'this is fatal'
})
})
test('set the base to null and use a formatter', async ({ equal, same }) => {
const stream = sink()
const instance = pino({
base: null,
formatters: {
log (input) {
return Object.assign({}, input, { additionalMessage: 'using pino' })
}
}
}, stream)
instance.fatal('this is fatal too')
const result = await once(stream, 'data')
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
delete result.time
same(result, {
level: 60,
msg: 'this is fatal too',
additionalMessage: 'using pino'
})
})
test('throw if creating child without bindings', async ({ equal, fail }) => {
const stream = sink()
const instance = pino(stream)
try {
instance.child()
fail('it should throw')
} catch (err) {
equal(err.message, 'missing bindings for child Pino')
}
})
test('correctly escapes msg strings with stray double quote at end', async ({ same }) => {
const stream = sink()
const instance = pino({
name: 'hello'
}, stream)
instance.fatal('this contains "')
const result = await once(stream, 'data')
delete result.time
same(result, {
pid,
hostname,
level: 60,
name: 'hello',
msg: 'this contains "'
})
})
test('correctly escape msg strings with unclosed double quote', async ({ same }) => {
const stream = sink()
const instance = pino({
name: 'hello'
}, stream)
instance.fatal('" this contains')
const result = await once(stream, 'data')
delete result.time
same(result, {
pid,
hostname,
level: 60,
name: 'hello',
msg: '" this contains'
})
})
test('correctly escape quote in a key', async ({ same }) => {
const stream = sink()
const instance = pino(stream)
const obj = { 'some"obj': 'world' }
instance.info(obj, 'a string')
const result = await once(stream, 'data')
delete result.time
same(result, {
level: 30,
pid,
hostname,
msg: 'a string',
'some"obj': 'world'
})
same(Object.keys(obj), ['some"obj'])
})
// https://github.com/pinojs/pino/issues/139
test('object and format string', async ({ same }) => {
const stream = sink()
const instance = pino(stream)
instance.info({}, 'foo %s', 'bar')
const result = await once(stream, 'data')
delete result.time
same(result, {
pid,
hostname,
level: 30,
msg: 'foo bar'
})
})
test('object and format string property', async ({ same }) => {
const stream = sink()
const instance = pino(stream)
instance.info({ answer: 42 }, 'foo %s', 'bar')
const result = await once(stream, 'data')
delete result.time
same(result, {
pid,
hostname,
level: 30,
msg: 'foo bar',
answer: 42
})
})
test('correctly strip undefined when returned from toJSON', async ({ equal }) => {
const stream = sink()
const instance = pino({
test: 'this'
}, stream)
instance.fatal({ test: { toJSON () { return undefined } } })
const result = await once(stream, 'data')
equal('test' in result, false)
})
test('correctly supports stderr', async ({ same }) => {
// stderr inherits from Stream, rather than Writable
const dest = {
writable: true,
write (result) {
result = JSON.parse(result)
delete result.time
same(result, {
pid,
hostname,
level: 60,
msg: 'a message'
})
}
}
const instance = pino(dest)
instance.fatal('a message')
})
test('normalize number to string', async ({ same }) => {
const stream = sink()
const instance = pino(stream)
instance.info(1)
const result = await once(stream, 'data')
delete result.time
same(result, {
pid,
hostname,
level: 30,
msg: '1'
})
})
test('normalize number to string with an object', async ({ same }) => {
const stream = sink()
const instance = pino(stream)
instance.info({ answer: 42 }, 1)
const result = await once(stream, 'data')
delete result.time
same(result, {
pid,
hostname,
level: 30,
msg: '1',
answer: 42
})
})
test('handles objects with null prototype', async ({ same }) => {
const stream = sink()
const instance = pino(stream)
const o = Object.create(null)
o.test = 'test'
instance.info(o)
const result = await once(stream, 'data')
delete result.time
same(result, {
pid,
hostname,
level: 30,
test: 'test'
})
})
test('pino.destination', async ({ same }) => {
const tmp = file()
const instance = pino(pino.destination(tmp))
instance.info('hello')
await watchFileCreated(tmp)
const result = JSON.parse(readFileSync(tmp).toString())
delete result.time
same(result, {
pid,
hostname,
level: 30,
msg: 'hello'
})
})
test('auto pino.destination with a string', async ({ same }) => {
const tmp = file()
const instance = pino(tmp)
instance.info('hello')
await watchFileCreated(tmp)
const result = JSON.parse(readFileSync(tmp).toString())
delete result.time
same(result, {
pid,
hostname,
level: 30,
msg: 'hello'
})
})
test('auto pino.destination with a string as second argument', async ({ same }) => {
const tmp = file()
const instance = pino(null, tmp)
instance.info('hello')
await watchFileCreated(tmp)
const result = JSON.parse(readFileSync(tmp).toString())
delete result.time
same(result, {
pid,
hostname,
level: 30,
msg: 'hello'
})
})
test('does not override opts with a string as second argument', async ({ same }) => {
const tmp = file()
const instance = pino({
timestamp: () => ',"time":"none"'
}, tmp)
instance.info('hello')
await watchFileCreated(tmp)
const result = JSON.parse(readFileSync(tmp).toString())
same(result, {
pid,
hostname,
level: 30,
time: 'none',
msg: 'hello'
})
})
// https://github.com/pinojs/pino/issues/222
test('children with same names render in correct order', async ({ equal }) => {
const stream = sink()
const root = pino(stream)
root.child({ a: 1 }).child({ a: 2 }).info({ a: 3 })
const { a } = await once(stream, 'data')
equal(a, 3, 'last logged object takes precedence')
})
test('use `safe-stable-stringify` to avoid circular dependencies', async ({ same }) => {
const stream = sink()
const root = pino(stream)
// circular depth
const obj = {}
obj.a = obj
root.info(obj)
const { a } = await once(stream, 'data')
same(a, { a: '[Circular]' })
})
test('correctly log non circular objects', async ({ same }) => {
const stream = sink()
const root = pino(stream)
const obj = {}
let parent = obj
for (let i = 0; i < 10; i++) {
parent.node = {}
parent = parent.node
}
root.info(obj)
const { node } = await once(stream, 'data')
same(node, { node: { node: { node: { node: { node: { node: { node: { node: { node: {} } } } } } } } } })
})
test('safe-stable-stringify must be used when interpolating', async (t) => {
const stream = sink()
const instance = pino(stream)
const o = { a: { b: {} } }
o.a.b.c = o.a.b
instance.info('test %j', o)
const { msg } = await once(stream, 'data')
t.equal(msg, 'test {"a":{"b":{"c":"[Circular]"}}}')
})
test('throws when setting useOnlyCustomLevels without customLevels', async ({ throws }) => {
throws(() => {
pino({
useOnlyCustomLevels: true
})
}, 'customLevels is required if useOnlyCustomLevels is set true')
})
test('correctly log Infinity', async (t) => {
const stream = sink()
const instance = pino(stream)
const o = { num: Infinity }
instance.info(o)
const { num } = await once(stream, 'data')
t.equal(num, null)
})
test('correctly log -Infinity', async (t) => {
const stream = sink()
const instance = pino(stream)
const o = { num: -Infinity }
instance.info(o)
const { num } = await once(stream, 'data')
t.equal(num, null)
})
test('correctly log NaN', async (t) => {
const stream = sink()
const instance = pino(stream)
const o = { num: NaN }
instance.info(o)
const { num } = await once(stream, 'data')
t.equal(num, null)
})
test('offers a .default() method to please typescript', async ({ equal }) => {
equal(pino.default, pino)
const stream = sink()
const instance = pino.default(stream)
instance.info('hello world')
check(equal, await once(stream, 'data'), 30, 'hello world')
})
test('correctly skip function', async (t) => {
const stream = sink()
const instance = pino(stream)
const o = { num: NaN }
instance.info(o, () => {})
const { msg } = await once(stream, 'data')
t.equal(msg, undefined)
})
test('correctly skip Infinity', async (t) => {
const stream = sink()
const instance = pino(stream)
const o = { num: NaN }
instance.info(o, Infinity)
const { msg } = await once(stream, 'data')
t.equal(msg, null)
})
test('correctly log number', async (t) => {
const stream = sink()
const instance = pino(stream)
const o = { num: NaN }
instance.info(o, 42)
const { msg } = await once(stream, 'data')
t.equal(msg, 42)
})
test('nestedKey should not be used for non-objects', async ({ strictSame }) => {
const stream = sink()
const message = 'hello'
const nestedKey = 'stuff'
const instance = pino({
nestedKey
}, stream)
instance.info(message)
const result = await once(stream, 'data')
delete result.time
strictSame(result, {
pid,
hostname,
level: 30,
msg: message
})
})
test('throws if prettyPrint is passed in as an option', async (t) => {
t.throws(() => {
pino({
prettyPrint: true
})
}, new Error('prettyPrint option is no longer supported, see the pino-pretty package (https://github.com/pinojs/pino-pretty)'))
})
test('Should invoke `onChild` with the newly created child', async ({ equal }) => {
let innerChild
const child = pino({
onChild: (instance) => {
innerChild = instance
}
}).child({ foo: 'bar' })
equal(child, innerChild)
})
test('logger message should have the prefix message that defined in the logger creation', async ({ equal }) => {
const stream = sink()
const logger = pino({
msgPrefix: 'My name is Bond '
}, stream)
logger.info('James Bond')
const { msg } = await once(stream, 'data')
equal(msg, 'My name is Bond James Bond')
})
test('child message should have the prefix message that defined in the child creation', async ({ equal }) => {
const stream = sink()
const instance = pino(stream)
const child = instance.child({}, { msgPrefix: 'My name is Bond ' })
child.info('James Bond')
const { msg } = await once(stream, 'data')
equal(msg, 'My name is Bond James Bond')
})
test('child message should have the prefix message that defined in the child creation when logging with log meta', async ({ equal }) => {
const stream = sink()
const instance = pino(stream)
const child = instance.child({}, { msgPrefix: 'My name is Bond ' })
child.info({ hello: 'world' }, 'James Bond')
const { msg, hello } = await once(stream, 'data')
equal(hello, 'world')
equal(msg, 'My name is Bond James Bond')
})
test('logged message should not have the prefix when not providing any message', async ({ equal }) => {
const stream = sink()
const instance = pino(stream)
const child = instance.child({}, { msgPrefix: 'This should not be shown ' })
child.info({ hello: 'world' })
const { msg, hello } = await once(stream, 'data')
equal(hello, 'world')
equal(msg, undefined)
})
test('child message should append parent prefix to current prefix that defined in the child creation', async ({ equal }) => {
const stream = sink()
const instance = pino({
msgPrefix: 'My name is Bond '
}, stream)
const child = instance.child({}, { msgPrefix: 'James ' })
child.info('Bond')
const { msg } = await once(stream, 'data')
equal(msg, 'My name is Bond James Bond')
})
test('child message should inherent parent prefix', async ({ equal }) => {
const stream = sink()
const instance = pino({
msgPrefix: 'My name is Bond '
}, stream)
const child = instance.child({})
child.info('James Bond')
const { msg } = await once(stream, 'data')
equal(msg, 'My name is Bond James Bond')
})
test('grandchild message should inherent parent prefix', async ({ equal }) => {
const stream = sink()
const instance = pino(stream)
const child = instance.child({}, { msgPrefix: 'My name is Bond ' })
const grandchild = child.child({})
grandchild.info('James Bond')
const { msg } = await once(stream, 'data')
equal(msg, 'My name is Bond James Bond')
})