dev-api: add developer portal, OpenAPI spec, and Flatenbadet case study
- New /developers/ page with API docs, SDKs, pricing, use cases - OpenAPI 3.0 spec for Orders, Missions, Photos, Analytics - Case study: Glasskiosken i Flatenbadet — complete ROI analysis - Updated /order/ with recurring missions and frequency dropdown
This commit is contained in:
+1088
@@ -0,0 +1,1088 @@
|
||||
'use strict'
|
||||
|
||||
process.env.TZ = 'UTC'
|
||||
|
||||
const { Writable } = require('readable-stream')
|
||||
const os = require('os')
|
||||
const test = require('tap').test
|
||||
const pino = require('pino')
|
||||
const dateformat = require('dateformat')
|
||||
const path = require('path')
|
||||
const rimraf = require('rimraf')
|
||||
const { join } = require('path')
|
||||
const fs = require('fs')
|
||||
const pinoPretty = require('..')
|
||||
const SonicBoom = require('sonic-boom')
|
||||
const _prettyFactory = pinoPretty.prettyFactory
|
||||
|
||||
// Disable pino warnings
|
||||
process.removeAllListeners('warning')
|
||||
|
||||
function prettyFactory (opts) {
|
||||
if (!opts) {
|
||||
opts = { colorize: false }
|
||||
} else if (!Object.prototype.hasOwnProperty.call(opts, 'colorize')) {
|
||||
opts.colorize = false
|
||||
}
|
||||
return _prettyFactory(opts)
|
||||
}
|
||||
|
||||
// All dates are computed from 'Fri, 30 Mar 2018 17:35:28 GMT'
|
||||
const epoch = 1522431328992
|
||||
const formattedEpoch = '17:35:28.992'
|
||||
const pid = process.pid
|
||||
const hostname = os.hostname()
|
||||
|
||||
test('basic prettifier tests', (t) => {
|
||||
t.beforeEach(() => {
|
||||
Date.originalNow = Date.now
|
||||
Date.now = () => epoch
|
||||
})
|
||||
t.afterEach(() => {
|
||||
Date.now = Date.originalNow
|
||||
delete Date.originalNow
|
||||
})
|
||||
|
||||
t.test('preserves output if not valid JSON', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const formatted = pretty('this is not json\nit\'s just regular output\n')
|
||||
t.equal(formatted, 'this is not json\nit\'s just regular output\n\n')
|
||||
})
|
||||
|
||||
t.test('formats a line without any extra options', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] INFO (${pid}): foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info('foo')
|
||||
})
|
||||
|
||||
t.test('will add color codes', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ colorize: true })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] \u001B[32mINFO\u001B[39m (${pid}): \u001B[36mfoo\u001B[39m\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info('foo')
|
||||
})
|
||||
|
||||
t.test('will omit color codes from objects when colorizeObjects = false', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ colorize: true, singleLine: true, colorizeObjects: false })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] \u001B[32mINFO\u001B[39m (${pid}): \u001B[36mfoo\u001B[39m {"foo":"bar"}\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ foo: 'bar' }, 'foo')
|
||||
})
|
||||
|
||||
t.test('can swap date and level position', (t) => {
|
||||
t.plan(1)
|
||||
const destination = new Writable({
|
||||
write (formatted, enc, cb) {
|
||||
t.equal(
|
||||
formatted.toString(),
|
||||
`INFO [${formattedEpoch}] (${pid}): foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
})
|
||||
const pretty = pinoPretty({
|
||||
destination,
|
||||
levelFirst: true,
|
||||
colorize: false
|
||||
})
|
||||
const log = pino({}, pretty)
|
||||
log.info('foo')
|
||||
})
|
||||
|
||||
t.test('can print message key value when its a string', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] INFO (${pid}): baz\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info('baz')
|
||||
})
|
||||
|
||||
t.test('can print message key value when its a number', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] INFO (${pid}): 42\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info(42)
|
||||
})
|
||||
|
||||
t.test('can print message key value when its a Number(0)', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] INFO (${pid}): 0\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info(0)
|
||||
})
|
||||
|
||||
t.test('can print message key value when its a boolean', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] INFO (${pid}): true\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info(true)
|
||||
})
|
||||
|
||||
t.test('can use different message keys', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ messageKey: 'bar' })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] INFO (${pid}): baz\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ bar: 'baz' })
|
||||
})
|
||||
|
||||
t.test('can use different level keys', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ levelKey: 'bar' })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] WARN (${pid}): foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ msg: 'foo', bar: 'warn' })
|
||||
})
|
||||
|
||||
t.test('can use a customPrettifier on default level output', (t) => {
|
||||
t.plan(1)
|
||||
const veryCustomLevels = {
|
||||
30: 'ok',
|
||||
40: 'not great'
|
||||
}
|
||||
const customPrettifiers = {
|
||||
level: (level) => `LEVEL: ${veryCustomLevels[level]}`
|
||||
}
|
||||
const pretty = prettyFactory({ customPrettifiers })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] LEVEL: ok (${pid}): foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ msg: 'foo' })
|
||||
})
|
||||
|
||||
t.test('can use a customPrettifier on different-level-key output', (t) => {
|
||||
t.plan(1)
|
||||
const customPrettifiers = {
|
||||
level: (level) => `LEVEL: ${level.toUpperCase()}`
|
||||
}
|
||||
const pretty = prettyFactory({ levelKey: 'bar', customPrettifiers })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] LEVEL: WARN (${pid}): foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ msg: 'foo', bar: 'warn' })
|
||||
})
|
||||
|
||||
t.test('can use a customPrettifier on name output', (t) => {
|
||||
t.plan(1)
|
||||
const customPrettifiers = {
|
||||
name: (hostname) => `NAME: ${hostname}`
|
||||
}
|
||||
const pretty = prettyFactory({ customPrettifiers })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] INFO (NAME: logger/${pid}): foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
const child = log.child({ name: 'logger' })
|
||||
child.info({ msg: 'foo' })
|
||||
})
|
||||
|
||||
t.test('can use a customPrettifier on hostname and pid output', (t) => {
|
||||
t.plan(1)
|
||||
const customPrettifiers = {
|
||||
hostname: (hostname) => `HOSTNAME: ${hostname}`,
|
||||
pid: (pid) => `PID: ${pid}`
|
||||
}
|
||||
const pretty = prettyFactory({ customPrettifiers, ignore: '' })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] INFO (PID: ${pid} on HOSTNAME: ${hostname}): foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ msg: 'foo' })
|
||||
})
|
||||
|
||||
t.test('can use a customPrettifier on default time output', (t) => {
|
||||
t.plan(1)
|
||||
const customPrettifiers = {
|
||||
time: (timestamp) => `TIME: ${timestamp}`
|
||||
}
|
||||
const pretty = prettyFactory({ customPrettifiers })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`TIME: ${formattedEpoch} INFO (${pid}): foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info('foo')
|
||||
})
|
||||
|
||||
t.test('can use a customPrettifier on the caller', (t) => {
|
||||
t.plan(1)
|
||||
const customPrettifiers = {
|
||||
caller: (caller) => `CALLER: ${caller}`
|
||||
}
|
||||
const pretty = prettyFactory({ customPrettifiers })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] INFO (${pid}) <CALLER: test.js:10>: foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ msg: 'foo', caller: 'test.js:10' })
|
||||
})
|
||||
|
||||
t.test('can use a customPrettifier on translateTime-time output', (t) => {
|
||||
t.plan(1)
|
||||
const customPrettifiers = {
|
||||
time: (timestamp) => `TIME: ${timestamp}`
|
||||
}
|
||||
const pretty = prettyFactory({ customPrettifiers, translateTime: true })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`TIME: ${formattedEpoch} INFO (${pid}): foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info('foo')
|
||||
})
|
||||
|
||||
t.test('will format time to UTC', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ translateTime: true })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] INFO (${pid}): foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info('foo')
|
||||
})
|
||||
|
||||
t.test('will format time to UTC in custom format', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ translateTime: 'HH:MM:ss o' })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
const utcHour = dateformat(epoch, 'UTC:' + 'HH')
|
||||
const offset = dateformat(epoch, 'UTC:' + 'o')
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${utcHour}:35:28 ${offset}] INFO (${pid}): foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info('foo')
|
||||
})
|
||||
|
||||
t.test('will format time to local systemzone in ISO 8601 format', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ translateTime: 'sys:standard' })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
const localHour = dateformat(epoch, 'HH')
|
||||
const localMinute = dateformat(epoch, 'MM')
|
||||
const localDate = dateformat(epoch, 'yyyy-mm-dd')
|
||||
const offset = dateformat(epoch, 'o')
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${localDate} ${localHour}:${localMinute}:28.992 ${offset}] INFO (${pid}): foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info('foo')
|
||||
})
|
||||
|
||||
t.test('will format time to local systemzone in custom format', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({
|
||||
translateTime: 'SYS:yyyy/mm/dd HH:MM:ss o'
|
||||
})
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
const localHour = dateformat(epoch, 'HH')
|
||||
const localMinute = dateformat(epoch, 'MM')
|
||||
const localDate = dateformat(epoch, 'yyyy/mm/dd')
|
||||
const offset = dateformat(epoch, 'o')
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${localDate} ${localHour}:${localMinute}:28 ${offset}] INFO (${pid}): foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info('foo')
|
||||
})
|
||||
|
||||
// TODO: 2019-03-30 -- We don't really want the indentation in this case? Or at least some better formatting.
|
||||
t.test('handles missing time', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const formatted = pretty('{"hello":"world"}')
|
||||
t.equal(formatted, ' hello: "world"\n')
|
||||
})
|
||||
|
||||
t.test('handles missing pid, hostname and name', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const log = pino({ base: null }, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.match(formatted, /\[.*\] INFO: hello world/)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info('hello world')
|
||||
})
|
||||
|
||||
t.test('handles missing pid', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const name = 'test'
|
||||
const msg = 'hello world'
|
||||
const regex = new RegExp('\\[.*\\] INFO \\(' + name + '\\): ' + msg)
|
||||
|
||||
const opts = {
|
||||
base: {
|
||||
name,
|
||||
hostname
|
||||
}
|
||||
}
|
||||
const log = pino(opts, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.match(formatted, regex)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
|
||||
log.info(msg)
|
||||
})
|
||||
|
||||
t.test('handles missing hostname', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const name = 'test'
|
||||
const msg = 'hello world'
|
||||
const regex = new RegExp('\\[.*\\] INFO \\(' + name + '/' + pid + '\\): ' + msg)
|
||||
|
||||
const opts = {
|
||||
base: {
|
||||
name,
|
||||
pid: process.pid
|
||||
}
|
||||
}
|
||||
const log = pino(opts, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.match(formatted, regex)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
|
||||
log.info(msg)
|
||||
})
|
||||
|
||||
t.test('handles missing name', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const msg = 'hello world'
|
||||
const regex = new RegExp('\\[.*\\] INFO \\(' + process.pid + '\\): ' + msg)
|
||||
|
||||
const opts = {
|
||||
base: {
|
||||
hostname,
|
||||
pid: process.pid
|
||||
}
|
||||
}
|
||||
const log = pino(opts, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.match(formatted, regex)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
|
||||
log.info(msg)
|
||||
})
|
||||
|
||||
t.test('works without time', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const log = pino({ timestamp: null }, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(formatted, `INFO (${pid}): hello world\n`)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info('hello world')
|
||||
})
|
||||
|
||||
t.test('prettifies properties', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.match(formatted, ' a: "b"')
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ a: 'b' }, 'hello world')
|
||||
})
|
||||
|
||||
t.test('prettifies nested properties', (t) => {
|
||||
t.plan(6)
|
||||
const expectedLines = [
|
||||
' a: {',
|
||||
' "b": {',
|
||||
' "c": "d"',
|
||||
' }',
|
||||
' }'
|
||||
]
|
||||
const pretty = prettyFactory()
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
const lines = formatted.split('\n')
|
||||
t.equal(lines.length, expectedLines.length + 2)
|
||||
lines.shift(); lines.pop()
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
t.equal(lines[i], expectedLines[i])
|
||||
}
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ a: { b: { c: 'd' } } }, 'hello world')
|
||||
})
|
||||
|
||||
t.test('treats the name with care', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const log = pino({ name: 'matteo' }, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(formatted, `[${formattedEpoch}] INFO (matteo/${pid}): hello world\n`)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info('hello world')
|
||||
})
|
||||
|
||||
t.test('handles spec allowed primitives', (t) => {
|
||||
const pretty = prettyFactory()
|
||||
let formatted = pretty(null)
|
||||
t.equal(formatted, 'null\n')
|
||||
|
||||
formatted = pretty(true)
|
||||
t.equal(formatted, 'true\n')
|
||||
|
||||
formatted = pretty(false)
|
||||
t.equal(formatted, 'false\n')
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('handles numbers', (t) => {
|
||||
const pretty = prettyFactory()
|
||||
let formatted = pretty(2)
|
||||
t.equal(formatted, '2\n')
|
||||
|
||||
formatted = pretty(-2)
|
||||
t.equal(formatted, '-2\n')
|
||||
|
||||
formatted = pretty(0.2)
|
||||
t.equal(formatted, '0.2\n')
|
||||
|
||||
formatted = pretty(Infinity)
|
||||
t.equal(formatted, 'Infinity\n')
|
||||
|
||||
formatted = pretty(NaN)
|
||||
t.equal(formatted, 'NaN\n')
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('handles `undefined` input', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const formatted = pretty(undefined)
|
||||
t.equal(formatted, 'undefined\n')
|
||||
})
|
||||
|
||||
t.test('handles customLogLevel', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const log = pino({ customLevels: { testCustom: 35 } }, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.match(formatted, /USERLVL/)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.testCustom('test message')
|
||||
})
|
||||
|
||||
t.test('filter some lines based on minimumLevel', (t) => {
|
||||
t.plan(3)
|
||||
const pretty = prettyFactory({ minimumLevel: 'info' })
|
||||
const expected = [
|
||||
undefined,
|
||||
undefined,
|
||||
`[${formattedEpoch}] INFO (${pid}): baz\n`
|
||||
]
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
expected.shift()
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ msg: 'foo', level: 10 })
|
||||
log.info({ msg: 'bar', level: 20 })
|
||||
// only this line will be formatted
|
||||
log.info({ msg: 'baz', level: 30 })
|
||||
})
|
||||
|
||||
t.test('filter lines based on minimumLevel using custom levels and level key', (t) => {
|
||||
t.plan(3)
|
||||
const pretty = prettyFactory({ minimumLevel: 20, levelKey: 'bar' })
|
||||
const expected = [
|
||||
undefined,
|
||||
`[${formattedEpoch}] DEBUG (${pid}): bar\n`,
|
||||
`[${formattedEpoch}] INFO (${pid}): baz\n`
|
||||
]
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
expected.shift()
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ msg: 'foo', bar: 10 })
|
||||
log.info({ msg: 'bar', bar: 20 })
|
||||
log.info({ msg: 'baz', bar: 30 })
|
||||
})
|
||||
|
||||
t.test('formats a line with an undefined field', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const obj = JSON.parse(chunk.toString())
|
||||
// weird hack, but we should not crash
|
||||
obj.a = undefined
|
||||
const formatted = pretty(obj)
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] INFO (${pid}): foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info('foo')
|
||||
})
|
||||
|
||||
t.test('prettifies msg object', (t) => {
|
||||
t.plan(6)
|
||||
const expectedLines = [
|
||||
' msg: {',
|
||||
' "b": {',
|
||||
' "c": "d"',
|
||||
' }',
|
||||
' }'
|
||||
]
|
||||
const pretty = prettyFactory()
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
const lines = formatted.split('\n')
|
||||
t.equal(lines.length, expectedLines.length + 2)
|
||||
lines.shift(); lines.pop()
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
t.equal(lines[i], expectedLines[i])
|
||||
}
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ msg: { b: { c: 'd' } } })
|
||||
})
|
||||
|
||||
t.test('prettifies msg object with circular references', (t) => {
|
||||
t.plan(7)
|
||||
const expectedLines = [
|
||||
' msg: {',
|
||||
' "a": "[Circular]",',
|
||||
' "b": {',
|
||||
' "c": "d"',
|
||||
' }',
|
||||
' }'
|
||||
]
|
||||
const pretty = prettyFactory()
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
const lines = formatted.split('\n')
|
||||
t.equal(lines.length, expectedLines.length + 2)
|
||||
lines.shift(); lines.pop()
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
t.equal(lines[i], expectedLines[i])
|
||||
}
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
|
||||
const msg = { b: { c: 'd' } }
|
||||
msg.a = msg
|
||||
log.info({ msg })
|
||||
})
|
||||
|
||||
t.test('prettifies custom key', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({
|
||||
customPrettifiers: {
|
||||
foo: val => `${val}_baz\nmultiline`,
|
||||
cow: val => val.toUpperCase()
|
||||
}
|
||||
})
|
||||
const arst = pretty('{"msg":"hello world", "foo": "bar", "cow": "moo", "level":30}')
|
||||
t.equal(arst, 'INFO: hello world\n foo: bar_baz\n multiline\n cow: MOO\n')
|
||||
})
|
||||
|
||||
t.test('does not add trailing space if prettified value begins with eol', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({
|
||||
customPrettifiers: {
|
||||
calls: val => '\n' + val.map(it => ' ' + it).join('\n')
|
||||
}
|
||||
})
|
||||
const arst = pretty('{"msg":"doing work","calls":["step 1","step 2","step 3"],"level":30}')
|
||||
t.equal(arst, 'INFO: doing work\n calls:\n step 1\n step 2\n step 3\n')
|
||||
})
|
||||
|
||||
t.test('does not prettify custom key that does not exists', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({
|
||||
customPrettifiers: {
|
||||
foo: val => `${val}_baz`,
|
||||
cow: val => val.toUpperCase()
|
||||
}
|
||||
})
|
||||
const arst = pretty('{"msg":"hello world", "foo": "bar", "level":30}')
|
||||
t.equal(arst, 'INFO: hello world\n foo: bar_baz\n')
|
||||
})
|
||||
|
||||
t.test('prettifies object with some undefined values', (t) => {
|
||||
t.plan(1)
|
||||
const destination = new Writable({
|
||||
write (chunk, _, cb) {
|
||||
t.equal(
|
||||
chunk + '',
|
||||
`[${formattedEpoch}] INFO (${pid}):\n a: {\n "b": "c"\n }\n n: null\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
})
|
||||
const pretty = pinoPretty({
|
||||
destination,
|
||||
colorize: false
|
||||
})
|
||||
const log = pino({}, pretty)
|
||||
log.info({
|
||||
a: { b: 'c' },
|
||||
s: Symbol.for('s'),
|
||||
f: f => f,
|
||||
c: class C {},
|
||||
n: null,
|
||||
err: { toJSON () {} }
|
||||
})
|
||||
})
|
||||
|
||||
t.test('ignores multiple keys', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ ignore: 'pid,hostname' })
|
||||
const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
|
||||
t.equal(arst, `[${formattedEpoch}] INFO: hello world\n`)
|
||||
})
|
||||
|
||||
t.test('ignores a single key', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ ignore: 'pid' })
|
||||
const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
|
||||
t.equal(arst, `[${formattedEpoch}] INFO (on ${hostname}): hello world\n`)
|
||||
})
|
||||
|
||||
t.test('ignores time', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ ignore: 'time' })
|
||||
const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
|
||||
t.equal(arst, `INFO (${pid} on ${hostname}): hello world\n`)
|
||||
})
|
||||
|
||||
t.test('ignores time and level', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ ignore: 'time,level' })
|
||||
const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
|
||||
t.equal(arst, `(${pid} on ${hostname}): hello world\n`)
|
||||
})
|
||||
|
||||
t.test('ignores all keys but message', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ ignore: 'time,level,name,pid,hostname' })
|
||||
const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
|
||||
t.equal(arst, 'hello world\n')
|
||||
})
|
||||
|
||||
t.test('include nothing', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ include: '' })
|
||||
const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
|
||||
t.equal(arst, 'hello world\n')
|
||||
})
|
||||
|
||||
t.test('include multiple keys', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ include: 'time,level' })
|
||||
const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
|
||||
t.equal(arst, `[${formattedEpoch}] INFO: hello world\n`)
|
||||
})
|
||||
|
||||
t.test('include a single key', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ include: 'level' })
|
||||
const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
|
||||
t.equal(arst, 'INFO: hello world\n')
|
||||
})
|
||||
|
||||
t.test('include should override ignore', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ ignore: 'time,level', include: 'time,level' })
|
||||
const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
|
||||
t.equal(arst, `[${formattedEpoch}] INFO: hello world\n`)
|
||||
})
|
||||
|
||||
t.test('prettifies trace caller', (t) => {
|
||||
t.plan(1)
|
||||
const traceCaller = (instance) => {
|
||||
const { symbols: { asJsonSym } } = pino
|
||||
const get = (target, name) => name === asJsonSym ? asJson : target[name]
|
||||
|
||||
function asJson (...args) {
|
||||
args[0] = args[0] || {}
|
||||
args[0].caller = '/tmp/script.js'
|
||||
return instance[asJsonSym].apply(this, args)
|
||||
}
|
||||
|
||||
return new Proxy(instance, { get })
|
||||
}
|
||||
|
||||
const pretty = prettyFactory()
|
||||
const log = traceCaller(pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(
|
||||
formatted,
|
||||
`[${formattedEpoch}] INFO (${pid}) </tmp/script.js>: foo\n`
|
||||
)
|
||||
cb()
|
||||
}
|
||||
})))
|
||||
log.info('foo')
|
||||
})
|
||||
|
||||
t.test('handles specified timestampKey', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ timestampKey: '@timestamp' })
|
||||
const arst = pretty(`{"msg":"hello world", "@timestamp":${epoch}, "level":30}`)
|
||||
t.equal(arst, `[${formattedEpoch}] INFO: hello world\n`)
|
||||
})
|
||||
|
||||
t.test('keeps "v" key in log', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ ignore: 'time' })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(formatted, `INFO (${pid} on ${hostname}):\n v: 1\n`)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ v: 1 })
|
||||
})
|
||||
|
||||
t.test('Hide object `{ key: "value" }` from output when flag `hideObject` is set', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ hideObject: true })
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.equal(formatted, `[${formattedEpoch}] INFO (${pid}): hello world\n`)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ key: 'value' }, 'hello world')
|
||||
})
|
||||
|
||||
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.equal(formatted, `[${formattedEpoch}] INFO (${pid}): 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.equal(formatted, `[${formattedEpoch}] INFO (${pid}): message\n`)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
log.info({ msg: 'message' })
|
||||
})
|
||||
|
||||
t.test('default options', (t) => {
|
||||
t.plan(1)
|
||||
t.doesNotThrow(pinoPretty)
|
||||
})
|
||||
|
||||
t.test('does not call fs.close on stdout stream', (t) => {
|
||||
t.plan(2)
|
||||
const destination = pino.destination({ minLength: 4096, sync: true })
|
||||
const prettyDestination = pinoPretty({ destination, colorize: false })
|
||||
const log = pino(prettyDestination)
|
||||
log.info('this message has been buffered')
|
||||
const chunks = []
|
||||
const { close, writeSync } = fs
|
||||
let closeCalled = false
|
||||
fs.close = new Proxy(close, {
|
||||
apply: (target, self, args) => {
|
||||
closeCalled = true
|
||||
}
|
||||
})
|
||||
fs.writeSync = new Proxy(writeSync, {
|
||||
apply: (target, self, args) => {
|
||||
chunks.push(args[1])
|
||||
return args[1].length
|
||||
}
|
||||
})
|
||||
destination.end()
|
||||
Object.assign(fs, { close, writeSync })
|
||||
t.match(chunks.join(''), /INFO .+: this message has been buffered/)
|
||||
t.equal(closeCalled, false)
|
||||
})
|
||||
|
||||
t.test('stream usage', async (t) => {
|
||||
t.plan(1)
|
||||
const tmpDir = path.join(__dirname, '.tmp_' + Date.now())
|
||||
t.teardown(() => rimraf(tmpDir, noop))
|
||||
|
||||
const destination = join(tmpDir, 'output')
|
||||
|
||||
const pretty = pinoPretty({
|
||||
singleLine: true,
|
||||
colorize: false,
|
||||
mkdir: true,
|
||||
append: false,
|
||||
destination: new SonicBoom({ dest: destination, async: false, mkdir: true, append: true }),
|
||||
customPrettifiers: {
|
||||
upper: val => val.toUpperCase(),
|
||||
undef: () => undefined
|
||||
}
|
||||
})
|
||||
const log = pino(pretty)
|
||||
log.info({ msg: 'message', extra: { foo: 'bar', number: 42 }, upper: 'foobar', undef: 'this will not show up' })
|
||||
|
||||
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.test('sync option', async (t) => {
|
||||
t.plan(1)
|
||||
const tmpDir = path.join(__dirname, '.tmp_' + Date.now())
|
||||
t.teardown(() => rimraf(tmpDir, noop))
|
||||
|
||||
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 formatted = fs.readFileSync(destination, 'utf8')
|
||||
|
||||
t.equal(formatted, `[${formattedEpoch}] INFO (${pid}): message {"extra":{"foo":"bar","number":42},"upper":"foobar"}\n`)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
function watchFileCreated (filename) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const TIMEOUT = 2000
|
||||
const INTERVAL = 100
|
||||
const threshold = TIMEOUT / INTERVAL
|
||||
let counter = 0
|
||||
const interval = setInterval(() => {
|
||||
// On some CI runs file is created but not filled
|
||||
if (fs.existsSync(filename) && fs.statSync(filename).size !== 0) {
|
||||
clearInterval(interval)
|
||||
resolve()
|
||||
} else if (counter <= threshold) {
|
||||
counter++
|
||||
} else {
|
||||
clearInterval(interval)
|
||||
reject(new Error(`${filename} was not created.`))
|
||||
}
|
||||
}, INTERVAL)
|
||||
})
|
||||
}
|
||||
|
||||
function noop () {}
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
'use strict'
|
||||
|
||||
process.env.TZ = 'UTC'
|
||||
|
||||
const path = require('path')
|
||||
const spawn = require('child_process').spawn
|
||||
const test = require('tap').test
|
||||
const fs = require('fs')
|
||||
const rimraf = require('rimraf')
|
||||
|
||||
const bin = require.resolve('../bin')
|
||||
const logLine = '{"level":30,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n'
|
||||
const noop = () => {}
|
||||
|
||||
test('cli', (t) => {
|
||||
const tmpDir = path.join(__dirname, '.tmp_' + Date.now())
|
||||
fs.mkdirSync(tmpDir)
|
||||
|
||||
t.teardown(() => rimraf(tmpDir, noop))
|
||||
|
||||
t.test('loads and applies default config file: pino-pretty.config.js', (t) => {
|
||||
t.plan(1)
|
||||
// Set translateTime: true on run configuration
|
||||
const configFile = path.join(tmpDir, 'pino-pretty.config.js')
|
||||
fs.writeFileSync(configFile, 'module.exports = { translateTime: true }')
|
||||
const env = { TERM: 'dumb', TZ: 'UTC' }
|
||||
const child = spawn(process.argv[0], [bin], { env, cwd: tmpDir })
|
||||
// Validate that the time has been translated
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), '[17:35:28.992] INFO (42): hello world\n')
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => {
|
||||
fs.unlinkSync(configFile)
|
||||
child.kill()
|
||||
})
|
||||
})
|
||||
|
||||
t.test('loads and applies default config file: pino-pretty.config.cjs', (t) => {
|
||||
t.plan(1)
|
||||
// Set translateTime: true on run configuration
|
||||
const configFile = path.join(tmpDir, 'pino-pretty.config.cjs')
|
||||
fs.writeFileSync(configFile, 'module.exports = { translateTime: true }')
|
||||
// Tell the loader to expect ESM modules
|
||||
const packageJsonFile = path.join(tmpDir, 'package.json')
|
||||
fs.writeFileSync(packageJsonFile, JSON.stringify({ type: 'module' }, null, 4))
|
||||
const env = { TERM: ' dumb', TZ: 'UTC' }
|
||||
const child = spawn(process.argv[0], [bin], { env, cwd: tmpDir })
|
||||
// Validate that the time has been translated
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), '[17:35:28.992] INFO (42): hello world\n')
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => {
|
||||
fs.unlinkSync(configFile)
|
||||
fs.unlinkSync(packageJsonFile)
|
||||
child.kill()
|
||||
})
|
||||
})
|
||||
|
||||
t.test('loads and applies default config file: .pino-prettyrc', (t) => {
|
||||
t.plan(1)
|
||||
// Set translateTime: true on run configuration
|
||||
const configFile = path.join(tmpDir, '.pino-prettyrc')
|
||||
fs.writeFileSync(configFile, JSON.stringify({ translateTime: true }, null, 4))
|
||||
const env = { TERM: ' dumb', TZ: 'UTC' }
|
||||
const child = spawn(process.argv[0], [bin], { env, cwd: tmpDir })
|
||||
// Validate that the time has been translated
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), '[17:35:28.992] INFO (42): hello world\n')
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => {
|
||||
fs.unlinkSync(configFile)
|
||||
child.kill()
|
||||
})
|
||||
})
|
||||
|
||||
t.test('loads and applies default config file: .pino-prettyrc.json', (t) => {
|
||||
t.plan(1)
|
||||
// Set translateTime: true on run configuration
|
||||
const configFile = path.join(tmpDir, '.pino-prettyrc.json')
|
||||
fs.writeFileSync(configFile, JSON.stringify({ translateTime: true }, null, 4))
|
||||
const env = { TERM: ' dumb', TZ: 'UTC' }
|
||||
const child = spawn(process.argv[0], [bin], { env, cwd: tmpDir })
|
||||
// Validate that the time has been translated
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), '[17:35:28.992] INFO (42): hello world\n')
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => {
|
||||
fs.unlinkSync(configFile)
|
||||
child.kill()
|
||||
})
|
||||
})
|
||||
|
||||
t.test('loads and applies custom config file: pino-pretty.config.test.json', (t) => {
|
||||
t.plan(1)
|
||||
// Set translateTime: true on run configuration
|
||||
const configFile = path.join(tmpDir, 'pino-pretty.config.test.json')
|
||||
fs.writeFileSync(configFile, JSON.stringify({ translateTime: true }, null, 4))
|
||||
const env = { TERM: ' dumb', TZ: 'UTC' }
|
||||
const child = spawn(process.argv[0], [bin, '--config', configFile], { env, cwd: tmpDir })
|
||||
// Validate that the time has been translated
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), '[17:35:28.992] INFO (42): hello world\n')
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test('loads and applies custom config file: pino-pretty.config.test.js', (t) => {
|
||||
t.plan(1)
|
||||
// Set translateTime: true on run configuration
|
||||
const configFile = path.join(tmpDir, 'pino-pretty.config.test.js')
|
||||
fs.writeFileSync(configFile, 'module.exports = { translateTime: true }')
|
||||
const env = { TERM: ' dumb', TZ: 'UTC' }
|
||||
const child = spawn(process.argv[0], [bin, '--config', configFile], { env, cwd: tmpDir })
|
||||
// Validate that the time has been translated
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), '[17:35:28.992] INFO (42): hello world\n')
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
;['--messageKey', '-m'].forEach((optionName) => {
|
||||
t.test(`cli options override config options via ${optionName}`, (t) => {
|
||||
t.plan(1)
|
||||
// Set translateTime: true on run configuration
|
||||
const configFile = path.join(tmpDir, 'pino-pretty.config.js')
|
||||
fs.writeFileSync(configFile, `
|
||||
module.exports = {
|
||||
translateTime: true,
|
||||
messageKey: 'custom_msg'
|
||||
}
|
||||
`.trim())
|
||||
// Set messageKey: 'new_msg' using command line option
|
||||
const env = { TERM: ' dumb', TZ: 'UTC' }
|
||||
const child = spawn(process.argv[0], [bin, optionName, 'new_msg'], { env, cwd: tmpDir })
|
||||
// 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(), '[17:35:28.992] INFO (42): hello world\n')
|
||||
})
|
||||
child.stdin.write(logLine.replace(/"msg"/, '"new_msg"'))
|
||||
t.teardown(() => {
|
||||
fs.unlinkSync(configFile)
|
||||
child.kill()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.test('cli options with defaults can be overridden by config', (t) => {
|
||||
t.plan(1)
|
||||
// Set errorProps: '*' on run configuration
|
||||
const configFile = path.join(tmpDir, 'pino-pretty.config.js')
|
||||
fs.writeFileSync(configFile, `
|
||||
module.exports = {
|
||||
errorProps: '*'
|
||||
}
|
||||
`.trim())
|
||||
// Set messageKey: 'new_msg' using command line option
|
||||
const env = { TERM: ' dumb', TZ: 'UTC' }
|
||||
const child = spawn(process.argv[0], [bin], { env, cwd: tmpDir })
|
||||
// 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(), '[21:31:36.006] 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(() => {
|
||||
fs.unlinkSync(configFile)
|
||||
child.kill()
|
||||
})
|
||||
})
|
||||
|
||||
t.test('throws on missing config file', (t) => {
|
||||
t.plan(2)
|
||||
const args = [bin, '--config', 'pino-pretty.config.missing.json']
|
||||
const env = { TERM: ' dumb', TZ: 'UTC' }
|
||||
const child = spawn(process.argv[0], args, { env, cwd: tmpDir })
|
||||
child.on('close', (code) => t.equal(code, 1))
|
||||
child.stdout.pipe(process.stdout)
|
||||
child.stderr.setEncoding('utf8')
|
||||
let data = ''
|
||||
child.stderr.on('data', (chunk) => {
|
||||
data += chunk
|
||||
})
|
||||
child.on('close', function () {
|
||||
t.match(
|
||||
data.toString(), 'Error: Failed to load runtime configuration file: pino-pretty.config.missing.json')
|
||||
})
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test('throws on invalid default config file', (t) => {
|
||||
t.plan(2)
|
||||
const configFile = path.join(tmpDir, 'pino-pretty.config.js')
|
||||
fs.writeFileSync(configFile, 'module.exports = () => {}')
|
||||
const env = { TERM: ' dumb', TZ: 'UTC' }
|
||||
const child = spawn(process.argv[0], [bin], { env, cwd: tmpDir })
|
||||
child.on('close', (code) => t.equal(code, 1))
|
||||
child.stdout.pipe(process.stdout)
|
||||
child.stderr.setEncoding('utf8')
|
||||
let data = ''
|
||||
child.stderr.on('data', (chunk) => {
|
||||
data += chunk
|
||||
})
|
||||
child.on('close', function () {
|
||||
t.match(data, 'Error: Invalid runtime configuration file: pino-pretty.config.js')
|
||||
})
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test('throws on invalid custom config file', (t) => {
|
||||
t.plan(2)
|
||||
const configFile = path.join(tmpDir, 'pino-pretty.config.invalid.js')
|
||||
fs.writeFileSync(configFile, 'module.exports = () => {}')
|
||||
const args = [bin, '--config', path.relative(tmpDir, configFile)]
|
||||
const env = { TERM: ' dumb', TZ: 'UTC' }
|
||||
const child = spawn(process.argv[0], args, { env, cwd: tmpDir })
|
||||
child.on('close', (code) => t.equal(code, 1))
|
||||
child.stdout.pipe(process.stdout)
|
||||
child.stderr.setEncoding('utf8')
|
||||
let data = ''
|
||||
child.stderr.on('data', (chunk) => {
|
||||
data += chunk
|
||||
})
|
||||
child.on('close', function () {
|
||||
t.match(data, 'Error: Invalid runtime configuration file: pino-pretty.config.invalid.js')
|
||||
})
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test('test help', (t) => {
|
||||
t.plan(1)
|
||||
const env = { TERM: ' dumb', TZ: 'UTC' }
|
||||
const child = spawn(process.argv[0], [bin, '--help'], { env })
|
||||
const file = fs.readFileSync('help/help.txt').toString()
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), file)
|
||||
})
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
'use strict'
|
||||
|
||||
process.env.TZ = 'UTC'
|
||||
|
||||
const path = require('path')
|
||||
const spawn = require('child_process').spawn
|
||||
const test = require('tap').test
|
||||
|
||||
const bin = require.resolve(path.join(__dirname, '..', 'bin.js'))
|
||||
const epoch = 1522431328992
|
||||
const logLine = '{"level":30,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n'
|
||||
const env = { TERM: 'dumb', TZ: 'UTC' }
|
||||
const formattedEpoch = '17:35:28.992'
|
||||
|
||||
test('cli', (t) => {
|
||||
t.test('does basic reformatting', (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
;['--levelFirst', '-l'].forEach((optionName) => {
|
||||
t.test(`flips epoch and level via ${optionName}`, (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, optionName], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `INFO [${formattedEpoch}] (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
})
|
||||
|
||||
;['--translateTime', '-t'].forEach((optionName) => {
|
||||
t.test(`translates time to default format via ${optionName}`, (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, optionName], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
})
|
||||
|
||||
;['--ignore', '-i'].forEach((optionName) => {
|
||||
t.test('does ignore multiple keys', (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, optionName, 'pid,hostname'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO: hello world\n`)
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
})
|
||||
|
||||
;['--customLevels', '-x'].forEach((optionName) => {
|
||||
t.test(`customize levels via ${optionName}`, (t) => {
|
||||
t.plan(1)
|
||||
const logLine = '{"level":1,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n'
|
||||
const child = spawn(process.argv[0], [bin, optionName, 'err:99,info:1'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test(`customize levels via ${optionName} without index`, (t) => {
|
||||
t.plan(1)
|
||||
const logLine = '{"level":1,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n'
|
||||
const child = spawn(process.argv[0], [bin, optionName, 'err:99,info'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test(`customize levels via ${optionName} with minimumLevel`, (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, '--minimumLevel', 'err', optionName, 'err:99,info:1'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] ERR (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write('{"level":1,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n')
|
||||
child.stdin.write('{"level":99,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n')
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test(`customize levels via ${optionName} with minimumLevel, customLevels and useOnlyCustomProps false`, (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, '--minimumLevel', 'custom', '--useOnlyCustomProps', 'false', optionName, 'custom:99,info:1'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] CUSTOM (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write('{"level":1,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n')
|
||||
child.stdin.write('{"level":99,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n')
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test(`customize levels via ${optionName} with minimumLevel, customLevels and useOnlyCustomProps true`, (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, '--minimumLevel', 'custom', '--useOnlyCustomProps', 'true', optionName, 'custom:99,info:1'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] CUSTOM (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write('{"level":1,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n')
|
||||
child.stdin.write('{"level":99,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n')
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
})
|
||||
|
||||
;['--customColors', '-X'].forEach((optionName) => {
|
||||
t.test(`customize levels via ${optionName}`, (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, optionName, 'info:blue,message:red'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test(`customize levels via ${optionName} with customLevels`, (t) => {
|
||||
t.plan(1)
|
||||
const logLine = '{"level":1,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n'
|
||||
const child = spawn(process.argv[0], [bin, '--customLevels', 'err:99,info', optionName, 'info:blue,message:red'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
})
|
||||
|
||||
;['--useOnlyCustomProps', '-U'].forEach((optionName) => {
|
||||
t.test(`customize levels via ${optionName} false and customColors`, (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, '--customColors', 'err:blue,info:red', optionName, 'false'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test(`customize levels via ${optionName} true and customColors`, (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, '--customColors', 'err:blue,info:red', optionName, 'true'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test(`customize levels via ${optionName} true and customLevels`, (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, '--customLevels', 'err:99,custom:30', optionName, 'true'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] CUSTOM (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test(`customize levels via ${optionName} true and no customLevels`, (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, optionName, 'true'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test(`customize levels via ${optionName} false and customLevels`, (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, '--customLevels', 'err:99,custom:25', optionName, 'false'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test(`customize levels via ${optionName} false and no customLevels`, (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, optionName, 'false'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO (42): hello world\n`)
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
})
|
||||
|
||||
t.test('does ignore escaped keys', (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, '-i', 'log\\.domain\\.corp/foo'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO: hello world\n`)
|
||||
})
|
||||
const logLine = '{"level":30,"time":1522431328992,"msg":"hello world","log.domain.corp/foo":"bar"}\n'
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test('passes through stringified date as string', (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin], { env })
|
||||
child.on('error', t.threw)
|
||||
|
||||
const date = JSON.stringify(new Date(epoch))
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), date + '\n')
|
||||
})
|
||||
|
||||
child.stdin.write(date)
|
||||
child.stdin.write('\n')
|
||||
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test('end stdin does not end the destination', (t) => {
|
||||
t.plan(2)
|
||||
const child = spawn(process.argv[0], [bin], { env })
|
||||
child.on('error', t.threw)
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), 'aaa\n')
|
||||
})
|
||||
|
||||
child.stdin.end('aaa\n')
|
||||
child.on('exit', function (code) {
|
||||
t.equal(code, 0)
|
||||
})
|
||||
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
;['--timestampKey', '-a'].forEach((optionName) => {
|
||||
t.test(`uses specified timestamp key via ${optionName}`, (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin, optionName, '@timestamp'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO: hello world\n`)
|
||||
})
|
||||
const logLine = '{"level":30,"@timestamp":1522431328992,"msg":"hello world"}\n'
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
})
|
||||
|
||||
;['--singleLine', '-S'].forEach((optionName) => {
|
||||
t.test(`singleLine=true via ${optionName}`, (t) => {
|
||||
t.plan(1)
|
||||
const logLineWithExtra = JSON.stringify(Object.assign(JSON.parse(logLine), {
|
||||
extra: {
|
||||
foo: 'bar',
|
||||
number: 42
|
||||
}
|
||||
})) + '\n'
|
||||
|
||||
const child = spawn(process.argv[0], [bin, optionName], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO (42): hello world {"extra":{"foo":"bar","number":42}}\n`)
|
||||
})
|
||||
child.stdin.write(logLineWithExtra)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
})
|
||||
|
||||
t.test('does ignore nested keys', (t) => {
|
||||
t.plan(1)
|
||||
|
||||
const logLineNested = JSON.stringify(Object.assign(JSON.parse(logLine), {
|
||||
extra: {
|
||||
foo: 'bar',
|
||||
number: 42,
|
||||
nested: {
|
||||
foo2: 'bar2'
|
||||
}
|
||||
}
|
||||
})) + '\n'
|
||||
|
||||
const child = spawn(process.argv[0], [bin, '-S', '-i', 'extra.foo,extra.nested,extra.nested.miss'], { env })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), `[${formattedEpoch}] INFO (42 on foo): hello world {"extra":{"number":42}}\n`)
|
||||
})
|
||||
child.stdin.write(logLineNested)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.test('change TZ', (t) => {
|
||||
t.plan(1)
|
||||
const child = spawn(process.argv[0], [bin], { env: { ...env, TZ: 'Europe/Amsterdam' } })
|
||||
child.on('error', t.threw)
|
||||
child.stdout.on('data', (data) => {
|
||||
t.equal(data.toString(), '[19:35:28.992] INFO (42): hello world\n')
|
||||
})
|
||||
child.stdin.write(logLine)
|
||||
t.teardown(() => child.kill())
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
'use strict'
|
||||
|
||||
process.env.TZ = 'UTC'
|
||||
|
||||
const test = require('tap').test
|
||||
const _prettyFactory = require('../').prettyFactory
|
||||
|
||||
function prettyFactory (opts) {
|
||||
if (!opts) {
|
||||
opts = { colorize: false }
|
||||
} else if (!Object.prototype.hasOwnProperty.call(opts, 'colorize')) {
|
||||
opts.colorize = false
|
||||
}
|
||||
return _prettyFactory(opts)
|
||||
}
|
||||
|
||||
const logLine = '{"level":30,"time":1522431328992,"msg":"hello world","pid":42,"hostname":"foo"}\n'
|
||||
|
||||
test('crlf', (t) => {
|
||||
t.test('uses LF by default', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory()
|
||||
const formatted = pretty(logLine)
|
||||
t.equal(formatted.substr(-2), 'd\n')
|
||||
})
|
||||
|
||||
t.test('can use CRLF', (t) => {
|
||||
t.plan(1)
|
||||
const pretty = prettyFactory({ crlf: true })
|
||||
const formatted = pretty(logLine)
|
||||
t.equal(formatted.substr(-3), 'd\r\n')
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
+454
@@ -0,0 +1,454 @@
|
||||
'use strict'
|
||||
|
||||
process.env.TZ = 'UTC'
|
||||
|
||||
const Writable = require('stream').Writable
|
||||
const test = require('tap').test
|
||||
const pino = require('pino')
|
||||
const serializers = pino.stdSerializers
|
||||
const _prettyFactory = require('../').prettyFactory
|
||||
|
||||
function prettyFactory (opts) {
|
||||
if (!opts) {
|
||||
opts = { colorize: false }
|
||||
} else if (!Object.prototype.hasOwnProperty.call(opts, 'colorize')) {
|
||||
opts.colorize = false
|
||||
}
|
||||
return _prettyFactory(opts)
|
||||
}
|
||||
|
||||
// All dates are computed from 'Fri, 30 Mar 2018 17:35:28 GMT'
|
||||
const epoch = 1522431328992
|
||||
const formattedEpoch = '17:35:28.992'
|
||||
const pid = process.pid
|
||||
|
||||
test('error like objects tests', (t) => {
|
||||
t.beforeEach(() => {
|
||||
Date.originalNow = Date.now
|
||||
Date.now = () => epoch
|
||||
})
|
||||
t.afterEach(() => {
|
||||
Date.now = Date.originalNow
|
||||
delete Date.originalNow
|
||||
})
|
||||
|
||||
t.test('pino transform prettifies Error', (t) => {
|
||||
t.plan(2)
|
||||
const pretty = prettyFactory()
|
||||
const err = Error('hello world')
|
||||
const expected = err.stack.split('\n')
|
||||
expected.unshift(err.message)
|
||||
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
const lines = formatted.split('\n')
|
||||
t.equal(lines.length, expected.length + 6)
|
||||
t.equal(lines[0], `[${formattedEpoch}] INFO (${pid}): hello world`)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
|
||||
log.info(err)
|
||||
})
|
||||
|
||||
t.test('errorProps recognizes user specified properties', (t) => {
|
||||
t.plan(3)
|
||||
const pretty = prettyFactory({ errorProps: 'statusCode,originalStack' })
|
||||
const log = pino({}, new Writable({
|
||||
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"/)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
|
||||
const error = Error('error message')
|
||||
error.stack = 'error stack'
|
||||
error.statusCode = 500
|
||||
error.originalStack = 'original stack'
|
||||
|
||||
log.error(error)
|
||||
})
|
||||
|
||||
t.test('prettifies ignores undefined errorLikeObject', (t) => {
|
||||
const pretty = prettyFactory()
|
||||
pretty({ err: undefined })
|
||||
pretty({ error: undefined })
|
||||
t.end()
|
||||
})
|
||||
|
||||
t.test('prettifies Error in property within errorLikeObjectKeys', (t) => {
|
||||
t.plan(8)
|
||||
const pretty = prettyFactory({
|
||||
errorLikeObjectKeys: ['err']
|
||||
})
|
||||
|
||||
const err = Error('hello world')
|
||||
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())
|
||||
const lines = formatted.split('\n')
|
||||
t.equal(lines.length, expected.length + 6)
|
||||
t.equal(lines[0], `[${formattedEpoch}] INFO (${pid}): 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",/)
|
||||
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 })
|
||||
})
|
||||
|
||||
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.equal(lines.length, expected.length + 5)
|
||||
t.equal(lines[0], `[${formattedEpoch}] INFO (${pid}): 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",/)
|
||||
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(4)
|
||||
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())
|
||||
const lines = formatted.split('\n')
|
||||
t.equal(lines.length, 3)
|
||||
t.equal(lines[0], `[${formattedEpoch}] INFO (${pid}): hello world`)
|
||||
t.equal(lines[1], ' err: error is hello world')
|
||||
t.equal(lines[2], '')
|
||||
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
|
||||
log.info({ err })
|
||||
})
|
||||
|
||||
t.test('prettifies Error in property within errorLikeObjectKeys when stack has escaped characters', (t) => {
|
||||
t.plan(8)
|
||||
const pretty = prettyFactory({
|
||||
errorLikeObjectKeys: ['err']
|
||||
})
|
||||
|
||||
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())
|
||||
const lines = formatted.split('\n')
|
||||
t.equal(lines.length, expected.length + 6)
|
||||
t.equal(lines[0], `[${formattedEpoch}] INFO (${pid}): 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",$/)
|
||||
t.match(lines[4], /\s{6}"stack":$/)
|
||||
t.match(lines[5], /\s{10}Error: hello world$/)
|
||||
t.match(lines[6], /\s{10}at anonymous \(C:\\project\\node_modules\\example\\index.js\)$/)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
|
||||
log.info({ err })
|
||||
})
|
||||
|
||||
t.test('prettifies Error in property within errorLikeObjectKeys when stack is not the last property', (t) => {
|
||||
t.plan(9)
|
||||
const pretty = prettyFactory({
|
||||
errorLikeObjectKeys: ['err']
|
||||
})
|
||||
|
||||
const err = Error('hello world')
|
||||
err.anotherField = 'dummy value'
|
||||
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())
|
||||
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}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>)/)
|
||||
t.match(lines[lines.length - 3], /\s{6}"anotherField": "dummy value"/)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
|
||||
log.info({ err })
|
||||
})
|
||||
|
||||
t.test('errorProps flag with "*" (print all nested props)', function (t) {
|
||||
const pretty = prettyFactory({ errorProps: '*' })
|
||||
const expectedLines = [
|
||||
' 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())
|
||||
const lines = formatted.split('\n')
|
||||
lines.shift(); lines.pop()
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
t.equal(lines[i], expectedLines[i])
|
||||
}
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
|
||||
const error = Error('error message')
|
||||
error.stack = 'error stack'
|
||||
error.statusCode = 500
|
||||
error.originalStack = 'original stack'
|
||||
error.dataBaseSpecificError = {
|
||||
erroMessage: 'some database error message',
|
||||
evenMoreSpecificStuff: {
|
||||
someErrorRelatedObject: 'error'
|
||||
}
|
||||
}
|
||||
|
||||
log.error(error)
|
||||
})
|
||||
|
||||
t.test('prettifies legacy error object at top level when singleLine=true', function (t) {
|
||||
t.plan(4)
|
||||
const pretty = prettyFactory({ singleLine: true })
|
||||
const err = Error('hello world')
|
||||
const expected = err.stack.split('\n')
|
||||
expected.unshift(err.message)
|
||||
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
const lines = formatted.split('\n')
|
||||
t.equal(lines.length, expected.length + 1)
|
||||
t.equal(lines[0], `[${formattedEpoch}] INFO (${pid}): ${expected[0]}`)
|
||||
t.equal(lines[1], ` ${expected[1]}`)
|
||||
t.equal(lines[2], ` ${expected[2]}`)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
|
||||
log.info({ type: 'Error', stack: err.stack, msg: err.message })
|
||||
})
|
||||
|
||||
t.test('errorProps: legacy error object at top level', function (t) {
|
||||
const pretty = prettyFactory({ errorProps: '*' })
|
||||
const expectedLines = [
|
||||
'INFO:',
|
||||
' error stack',
|
||||
' message: hello message',
|
||||
' statusCode: 500',
|
||||
' originalStack: original stack',
|
||||
' dataBaseSpecificError: {',
|
||||
' errorMessage: "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 = {
|
||||
errorMessage: '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()
|
||||
const log = pino({}, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
t.match(formatted, /\s{4}message: "foo"/)
|
||||
t.match(formatted, /\s{4}stack: null/)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
|
||||
const error = { message: 'foo', stack: null }
|
||||
log.error(error)
|
||||
})
|
||||
|
||||
t.test('handles errors with a null stack for Error object', (t) => {
|
||||
const pretty = prettyFactory()
|
||||
const expectedLines = [
|
||||
' "type": "Error",',
|
||||
' "message": "error message",',
|
||||
' "stack":',
|
||||
' ',
|
||||
' "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.shift(); lines.pop(); lines.pop()
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
t.ok(lines[i].includes(expectedLines[i]))
|
||||
}
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
|
||||
const error = Error('error message')
|
||||
error.stack = null
|
||||
error.some = 'property'
|
||||
|
||||
log.error(error)
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// Run this to see how colouring works
|
||||
|
||||
const _prettyFactory = require('../../')
|
||||
const pino = require('pino')
|
||||
const { Writable } = require('readable-stream')
|
||||
|
||||
function prettyFactory () {
|
||||
return _prettyFactory({
|
||||
colorize: true
|
||||
})
|
||||
}
|
||||
|
||||
const pretty = prettyFactory()
|
||||
const formatted = pretty('this is not json\nit\'s just regular output\n')
|
||||
console.log(formatted)
|
||||
|
||||
const opts = {
|
||||
base: {
|
||||
hostname: 'localhost',
|
||||
pid: process.pid
|
||||
}
|
||||
}
|
||||
const log = pino(opts, new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
const formatted = pretty(chunk.toString())
|
||||
console.log(formatted)
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
|
||||
log.info('foobar')
|
||||
Generated
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
import { expectType } from "tsd";
|
||||
|
||||
import pretty from "../../";
|
||||
import PinoPretty, {
|
||||
PinoPretty as PinoPrettyNamed,
|
||||
PrettyOptions,
|
||||
colorizerFactory,
|
||||
prettyFactory
|
||||
} from "../../";
|
||||
import PinoPrettyDefault from "../../";
|
||||
import * as PinoPrettyStar from "../../";
|
||||
import PinoPrettyCjsImport = require("../../");
|
||||
import PrettyStream = PinoPretty.PrettyStream;
|
||||
const PinoPrettyCjs = require("../../");
|
||||
|
||||
const options: PinoPretty.PrettyOptions = {
|
||||
colorize: true,
|
||||
crlf: false,
|
||||
errorLikeObjectKeys: ["err", "error"],
|
||||
errorProps: "",
|
||||
hideObject: true,
|
||||
levelKey: "level",
|
||||
levelLabel: "foo",
|
||||
messageFormat: false,
|
||||
ignore: "",
|
||||
levelFirst: false,
|
||||
messageKey: "msg",
|
||||
timestampKey: "timestamp",
|
||||
minimumLevel: "trace",
|
||||
translateTime: "UTC:h:MM:ss TT Z",
|
||||
singleLine: false,
|
||||
customPrettifiers: {
|
||||
key: (value) => {
|
||||
return value.toString().toUpperCase();
|
||||
}
|
||||
},
|
||||
customLevels: 'verbose:5',
|
||||
customColors: 'default:white,verbose:gray',
|
||||
sync: false,
|
||||
destination: 2,
|
||||
append: true,
|
||||
mkdir: true,
|
||||
};
|
||||
|
||||
expectType<PrettyStream>(pretty()); // #326
|
||||
expectType<PrettyStream>(pretty(options));
|
||||
expectType<PrettyStream>(PinoPrettyNamed(options));
|
||||
expectType<PrettyStream>(PinoPrettyDefault(options));
|
||||
expectType<PrettyStream>(PinoPrettyStar.PinoPretty(options));
|
||||
expectType<PrettyStream>(PinoPrettyStar.default(options));
|
||||
expectType<PrettyStream>(PinoPrettyCjsImport.PinoPretty(options));
|
||||
expectType<PrettyStream>(PinoPrettyCjsImport.default(options));
|
||||
expectType<any>(PinoPrettyCjs(options));
|
||||
expectType<PinoPretty.ColorizerFactory>(colorizerFactory);
|
||||
expectType<PinoPretty.PrettyFactory>(prettyFactory);
|
||||
Reference in New Issue
Block a user