UNPKG

20.3 kBJavaScriptView Raw
1'use strict'
2
3const { Writable } = require('readable-stream')
4const os = require('os')
5const test = require('tap').test
6const pino = require('pino')
7const dateformat = require('dateformat')
8const _prettyFactory = require('../')
9
10function prettyFactory (opts) {
11 if (!opts) {
12 opts = { colorize: false }
13 } else if (!Object.prototype.hasOwnProperty.call(opts, 'colorize')) {
14 opts.colorize = false
15 }
16 return _prettyFactory(opts)
17}
18
19// All dates are computed from 'Fri, 30 Mar 2018 17:35:28 GMT'
20const epoch = 1522431328992
21const pid = process.pid
22const hostname = os.hostname()
23
24test('basic prettifier tests', (t) => {
25 t.beforeEach((done) => {
26 Date.originalNow = Date.now
27 Date.now = () => epoch
28
29 done()
30 })
31 t.afterEach((done) => {
32 Date.now = Date.originalNow
33 delete Date.originalNow
34 done()
35 })
36
37 t.test('preserves output if not valid JSON', (t) => {
38 t.plan(1)
39 const pretty = prettyFactory()
40 const formatted = pretty('this is not json\nit\'s just regular output\n')
41 t.is(formatted, 'this is not json\nit\'s just regular output\n\n')
42 })
43
44 t.test('formats a line without any extra options', (t) => {
45 t.plan(1)
46 const pretty = prettyFactory()
47 const log = pino({}, new Writable({
48 write (chunk, enc, cb) {
49 const formatted = pretty(chunk.toString())
50 t.is(
51 formatted,
52 `[${epoch}] INFO (${pid} on ${hostname}): foo\n`
53 )
54 cb()
55 }
56 }))
57 log.info('foo')
58 })
59
60 t.test('will add color codes', (t) => {
61 t.plan(1)
62 const pretty = prettyFactory({ colorize: true })
63 const log = pino({}, new Writable({
64 write (chunk, enc, cb) {
65 const formatted = pretty(chunk.toString())
66 t.is(
67 formatted,
68 `[${epoch}] \u001B[32mINFO\u001B[39m (${pid} on ${hostname}): \u001B[36mfoo\u001B[39m\n`
69 )
70 cb()
71 }
72 }))
73 log.info('foo')
74 })
75
76 t.test('can swap date and level position', (t) => {
77 t.plan(1)
78 const pretty = prettyFactory({ levelFirst: true })
79 const log = pino({}, new Writable({
80 write (chunk, enc, cb) {
81 const formatted = pretty(chunk.toString())
82 t.is(
83 formatted,
84 `INFO [${epoch}] (${pid} on ${hostname}): foo\n`
85 )
86 cb()
87 }
88 }))
89 log.info('foo')
90 })
91
92 t.test('can use different message keys', (t) => {
93 t.plan(1)
94 const pretty = prettyFactory({ messageKey: 'bar' })
95 const log = pino({}, new Writable({
96 write (chunk, enc, cb) {
97 const formatted = pretty(chunk.toString())
98 t.is(
99 formatted,
100 `[${epoch}] INFO (${pid} on ${hostname}): baz\n`
101 )
102 cb()
103 }
104 }))
105 log.info({ bar: 'baz' })
106 })
107
108 t.test('can use different level keys', (t) => {
109 t.plan(1)
110 const pretty = prettyFactory({ levelKey: 'bar' })
111 const log = pino({}, new Writable({
112 write (chunk, enc, cb) {
113 const formatted = pretty(chunk.toString())
114 t.is(
115 formatted,
116 `[${epoch}] WARN (${pid} on ${hostname}): foo\n`
117 )
118 cb()
119 }
120 }))
121 log.info({ msg: 'foo', bar: 'warn' })
122 })
123
124 t.test('will format time to UTC', (t) => {
125 t.plan(1)
126 const pretty = prettyFactory({ translateTime: true })
127 const log = pino({}, new Writable({
128 write (chunk, enc, cb) {
129 const formatted = pretty(chunk.toString())
130 t.is(
131 formatted,
132 `[2018-03-30 17:35:28.992 +0000] INFO (${pid} on ${hostname}): foo\n`
133 )
134 cb()
135 }
136 }))
137 log.info('foo')
138 })
139
140 t.test('will format time to UTC in custom format', (t) => {
141 t.plan(1)
142 const pretty = prettyFactory({ translateTime: 'HH:MM:ss o' })
143 const log = pino({}, new Writable({
144 write (chunk, enc, cb) {
145 const formatted = pretty(chunk.toString())
146 const utcHour = dateformat(epoch, 'UTC:' + 'HH')
147 const offset = dateformat(epoch, 'UTC:' + 'o')
148 t.is(
149 formatted,
150 `[${utcHour}:35:28 ${offset}] INFO (${pid} on ${hostname}): foo\n`
151 )
152 cb()
153 }
154 }))
155 log.info('foo')
156 })
157
158 t.test('will format time to local systemzone in ISO 8601 format', (t) => {
159 t.plan(1)
160 const pretty = prettyFactory({ translateTime: 'sys:standard' })
161 const log = pino({}, new Writable({
162 write (chunk, enc, cb) {
163 const formatted = pretty(chunk.toString())
164 const localHour = dateformat(epoch, 'HH')
165 const localDate = dateformat(epoch, 'yyyy-mm-dd')
166 const offset = dateformat(epoch, 'o')
167 t.is(
168 formatted,
169 `[${localDate} ${localHour}:35:28.992 ${offset}] INFO (${pid} on ${hostname}): foo\n`
170 )
171 cb()
172 }
173 }))
174 log.info('foo')
175 })
176
177 t.test('will format time to local systemzone in custom format', (t) => {
178 t.plan(1)
179 const pretty = prettyFactory({
180 translateTime: 'SYS:yyyy/mm/dd HH:MM:ss o'
181 })
182 const log = pino({}, new Writable({
183 write (chunk, enc, cb) {
184 const formatted = pretty(chunk.toString())
185 const localHour = dateformat(epoch, 'HH')
186 const localDate = dateformat(epoch, 'yyyy/mm/dd')
187 const offset = dateformat(epoch, 'o')
188 t.is(
189 formatted,
190 `[${localDate} ${localHour}:35:28 ${offset}] INFO (${pid} on ${hostname}): foo\n`
191 )
192 cb()
193 }
194 }))
195 log.info('foo')
196 })
197
198 // TODO: 2019-03-30 -- We don't really want the indentation in this case? Or at least some better formatting.
199 t.test('handles missing time', (t) => {
200 t.plan(1)
201 const pretty = prettyFactory()
202 const formatted = pretty('{"hello":"world"}')
203 t.is(formatted, ' hello: "world"\n')
204 })
205
206 t.test('handles missing pid, hostname and name', (t) => {
207 t.plan(1)
208 const pretty = prettyFactory()
209 const log = pino({ base: null }, new Writable({
210 write (chunk, enc, cb) {
211 const formatted = pretty(chunk.toString())
212 t.match(formatted, /\[.*\] INFO: hello world/)
213 cb()
214 }
215 }))
216 log.info('hello world')
217 })
218
219 t.test('handles missing pid', (t) => {
220 t.plan(1)
221 const pretty = prettyFactory()
222 const name = 'test'
223 const msg = 'hello world'
224 const regex = new RegExp('\\[.*\\] INFO \\(' + name + ' on ' + hostname + '\\): ' + msg)
225
226 const opts = {
227 base: {
228 name: name,
229 hostname: hostname
230 }
231 }
232 const log = pino(opts, new Writable({
233 write (chunk, enc, cb) {
234 const formatted = pretty(chunk.toString())
235 t.match(formatted, regex)
236 cb()
237 }
238 }))
239
240 log.info(msg)
241 })
242
243 t.test('handles missing hostname', (t) => {
244 t.plan(1)
245 const pretty = prettyFactory()
246 const name = 'test'
247 const msg = 'hello world'
248 const regex = new RegExp('\\[.*\\] INFO \\(' + name + '/' + pid + '\\): ' + msg)
249
250 const opts = {
251 base: {
252 name: name,
253 pid: process.pid
254 }
255 }
256 const log = pino(opts, new Writable({
257 write (chunk, enc, cb) {
258 const formatted = pretty(chunk.toString())
259 t.match(formatted, regex)
260 cb()
261 }
262 }))
263
264 log.info(msg)
265 })
266
267 t.test('handles missing name', (t) => {
268 t.plan(1)
269 const pretty = prettyFactory()
270 const msg = 'hello world'
271 const regex = new RegExp('\\[.*\\] INFO \\(' + process.pid + ' on ' + hostname + '\\): ' + msg)
272
273 const opts = {
274 base: {
275 hostname: hostname,
276 pid: process.pid
277 }
278 }
279 const log = pino(opts, new Writable({
280 write (chunk, enc, cb) {
281 const formatted = pretty(chunk.toString())
282 t.match(formatted, regex)
283 cb()
284 }
285 }))
286
287 log.info(msg)
288 })
289
290 t.test('works without time', (t) => {
291 t.plan(1)
292 const pretty = prettyFactory()
293 const log = pino({ timestamp: null }, new Writable({
294 write (chunk, enc, cb) {
295 const formatted = pretty(chunk.toString())
296 t.is(formatted, `INFO (${pid} on ${hostname}): hello world\n`)
297 cb()
298 }
299 }))
300 log.info('hello world')
301 })
302
303 t.test('prettifies properties', (t) => {
304 t.plan(1)
305 const pretty = prettyFactory()
306 const log = pino({}, new Writable({
307 write (chunk, enc, cb) {
308 const formatted = pretty(chunk.toString())
309 t.match(formatted, ' a: "b"')
310 cb()
311 }
312 }))
313 log.info({ a: 'b' }, 'hello world')
314 })
315
316 t.test('prettifies nested properties', (t) => {
317 t.plan(6)
318 const expectedLines = [
319 ' a: {',
320 ' "b": {',
321 ' "c": "d"',
322 ' }',
323 ' }'
324 ]
325 const pretty = prettyFactory()
326 const log = pino({}, new Writable({
327 write (chunk, enc, cb) {
328 const formatted = pretty(chunk.toString())
329 const lines = formatted.split('\n')
330 t.is(lines.length, expectedLines.length + 2)
331 lines.shift(); lines.pop()
332 for (let i = 0; i < lines.length; i += 1) {
333 t.is(lines[i], expectedLines[i])
334 }
335 cb()
336 }
337 }))
338 log.info({ a: { b: { c: 'd' } } }, 'hello world')
339 })
340
341 t.test('treats the name with care', (t) => {
342 t.plan(1)
343 const pretty = prettyFactory()
344 const log = pino({ name: 'matteo' }, new Writable({
345 write (chunk, enc, cb) {
346 const formatted = pretty(chunk.toString())
347 t.is(formatted, `[${epoch}] INFO (matteo/${pid} on ${hostname}): hello world\n`)
348 cb()
349 }
350 }))
351 log.info('hello world')
352 })
353
354 t.test('handles spec allowed primitives', (t) => {
355 const pretty = prettyFactory()
356 let formatted = pretty(null)
357 t.is(formatted, 'null\n')
358
359 formatted = pretty(true)
360 t.is(formatted, 'true\n')
361
362 formatted = pretty(false)
363 t.is(formatted, 'false\n')
364
365 t.end()
366 })
367
368 t.test('handles numbers', (t) => {
369 const pretty = prettyFactory()
370 let formatted = pretty(2)
371 t.is(formatted, '2\n')
372
373 formatted = pretty(-2)
374 t.is(formatted, '-2\n')
375
376 formatted = pretty(0.2)
377 t.is(formatted, '0.2\n')
378
379 formatted = pretty(Infinity)
380 t.is(formatted, 'Infinity\n')
381
382 formatted = pretty(NaN)
383 t.is(formatted, 'NaN\n')
384
385 t.end()
386 })
387
388 t.test('handles `undefined` input', (t) => {
389 t.plan(1)
390 const pretty = prettyFactory()
391 const formatted = pretty(undefined)
392 t.is(formatted, 'undefined\n')
393 })
394
395 t.test('handles customLogLevel', (t) => {
396 t.plan(1)
397 const pretty = prettyFactory()
398 const log = pino({ customLevels: { testCustom: 35 } }, new Writable({
399 write (chunk, enc, cb) {
400 const formatted = pretty(chunk.toString())
401 t.match(formatted, /USERLVL/)
402 cb()
403 }
404 }))
405 log.testCustom('test message')
406 })
407
408 t.test('supports pino metadata API', (t) => {
409 t.plan(1)
410 const dest = new Writable({
411 write (chunk, enc, cb) {
412 t.is(
413 chunk.toString(),
414 `[${epoch}] INFO (${pid} on ${hostname}): foo\n`
415 )
416 cb()
417 }
418 })
419 const log = pino({
420 prettifier: prettyFactory,
421 prettyPrint: true
422 }, dest)
423 log.info('foo')
424 })
425
426 t.test('can swap date and level position through meta stream', (t) => {
427 t.plan(1)
428
429 const dest = new Writable({
430 objectMode: true,
431 write (formatted, enc, cb) {
432 t.is(
433 formatted,
434 `INFO [${epoch}] (${pid} on ${hostname}): foo\n`
435 )
436 cb()
437 }
438 })
439 const log = pino({
440 prettifier: prettyFactory,
441 prettyPrint: {
442 levelFirst: true
443 }
444 }, dest)
445 log.info('foo')
446 })
447
448 t.test('filter some lines based on jmespath', (t) => {
449 t.plan(3)
450 const pretty = prettyFactory({ search: 'foo.bar' })
451 const expected = [
452 undefined,
453 undefined,
454 `[${epoch}] INFO (${pid} on ${hostname}): foo\n foo: {\n "bar": true\n }\n`
455 ]
456 const log = pino({}, new Writable({
457 write (chunk, enc, cb) {
458 const formatted = pretty(chunk.toString())
459 t.is(
460 formatted,
461 expected.shift()
462 )
463 cb()
464 }
465 }))
466 log.info('foo')
467 log.info({ something: 'else' }, 'foo')
468 // only this line will be formatted
469 log.info({ foo: { bar: true } }, 'foo')
470 })
471
472 t.test('handles `undefined` return values', (t) => {
473 t.plan(2)
474 const pretty = prettyFactory({ search: 'msg == \'hello world\'' })
475 let formatted = pretty(`{"msg":"nope", "time":${epoch}, "level":30}`)
476 t.is(formatted, undefined)
477 formatted = pretty(`{"msg":"hello world", "time":${epoch}, "level":30}`)
478 t.is(formatted, `[${epoch}] INFO: hello world\n`)
479 })
480
481 t.test('formats a line with an undefined field', (t) => {
482 t.plan(1)
483 const pretty = prettyFactory()
484 const log = pino({}, new Writable({
485 write (chunk, enc, cb) {
486 const obj = JSON.parse(chunk.toString())
487 // weird hack, but we should not crash
488 obj.a = undefined
489 const formatted = pretty(obj)
490 t.is(
491 formatted,
492 `[${epoch}] INFO (${pid} on ${hostname}): foo\n`
493 )
494 cb()
495 }
496 }))
497 log.info('foo')
498 })
499
500 t.test('prettifies msg object', (t) => {
501 t.plan(6)
502 const expectedLines = [
503 ' msg: {',
504 ' "b": {',
505 ' "c": "d"',
506 ' }',
507 ' }'
508 ]
509 const pretty = prettyFactory()
510 const log = pino({}, new Writable({
511 write (chunk, enc, cb) {
512 const formatted = pretty(chunk.toString())
513 const lines = formatted.split('\n')
514 t.is(lines.length, expectedLines.length + 2)
515 lines.shift(); lines.pop()
516 for (let i = 0; i < lines.length; i += 1) {
517 t.is(lines[i], expectedLines[i])
518 }
519 cb()
520 }
521 }))
522 log.info({ msg: { b: { c: 'd' } } })
523 })
524
525 t.test('prettifies msg object with circular references', (t) => {
526 t.plan(7)
527 const expectedLines = [
528 ' msg: {',
529 ' "b": {',
530 ' "c": "d"',
531 ' },',
532 ' "a": "[Circular]"',
533 ' }'
534 ]
535 const pretty = prettyFactory()
536 const log = pino({}, new Writable({
537 write (chunk, enc, cb) {
538 const formatted = pretty(chunk.toString())
539 const lines = formatted.split('\n')
540 t.is(lines.length, expectedLines.length + 2)
541 lines.shift(); lines.pop()
542 for (let i = 0; i < lines.length; i += 1) {
543 t.is(lines[i], expectedLines[i])
544 }
545 cb()
546 }
547 }))
548
549 const msg = { b: { c: 'd' } }
550 msg.a = msg
551 log.info({ msg })
552 })
553
554 t.test('prettifies custom key', (t) => {
555 t.plan(1)
556 const pretty = prettyFactory({
557 customPrettifiers: {
558 foo: val => `${val}_baz\nmultiline`,
559 cow: val => val.toUpperCase()
560 }
561 })
562 const arst = pretty('{"msg":"hello world", "foo": "bar", "cow": "moo", "level":30}')
563 t.is(arst, 'INFO: hello world\n foo: bar_baz\n multiline\n cow: MOO\n')
564 })
565
566 t.test('does not prettify custom key that does not exists', (t) => {
567 t.plan(1)
568 const pretty = prettyFactory({
569 customPrettifiers: {
570 foo: val => `${val}_baz`,
571 cow: val => val.toUpperCase()
572 }
573 })
574 const arst = pretty('{"msg":"hello world", "foo": "bar", "level":30}')
575 t.is(arst, 'INFO: hello world\n foo: bar_baz\n')
576 })
577
578 t.test('prettifies object with some undefined values', (t) => {
579 t.plan(1)
580 const dest = new Writable({
581 write (chunk, _, cb) {
582 t.is(
583 chunk + '',
584 `[${epoch}] INFO (${pid} on ${hostname}):\n a: {\n "b": "c"\n }\n n: null\n`
585 )
586 cb()
587 }
588 })
589 const log = pino({
590 prettifier: prettyFactory,
591 prettyPrint: true
592 }, dest)
593 log.info({
594 a: { b: 'c' },
595 s: Symbol.for('s'),
596 f: f => f,
597 c: class C {},
598 n: null,
599 err: { toJSON () {} }
600 })
601 })
602
603 t.test('ignores multiple keys', (t) => {
604 t.plan(1)
605 const pretty = prettyFactory({ ignore: 'pid,hostname' })
606 const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
607 t.is(arst, `[${epoch}] INFO: hello world\n`)
608 })
609
610 t.test('ignores a single key', (t) => {
611 t.plan(1)
612 const pretty = prettyFactory({ ignore: 'pid' })
613 const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
614 t.is(arst, `[${epoch}] INFO (on ${hostname}): hello world\n`)
615 })
616
617 t.test('ignores time', (t) => {
618 t.plan(1)
619 const pretty = prettyFactory({ ignore: 'time' })
620 const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
621 t.is(arst, `INFO (${pid} on ${hostname}): hello world\n`)
622 })
623
624 t.test('ignores time and level', (t) => {
625 t.plan(1)
626 const pretty = prettyFactory({ ignore: 'time,level' })
627 const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
628 t.is(arst, `(${pid} on ${hostname}): hello world\n`)
629 })
630
631 t.test('ignores all keys but message', (t) => {
632 t.plan(1)
633 const pretty = prettyFactory({ ignore: 'time,level,name,pid,hostname' })
634 const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30}`)
635 t.is(arst, 'hello world\n')
636 })
637
638 t.test('prettifies trace caller', (t) => {
639 t.plan(1)
640 const traceCaller = (instance) => {
641 const { symbols: { asJsonSym } } = pino
642 const get = (target, name) => name === asJsonSym ? asJson : target[name]
643
644 function asJson (...args) {
645 args[0] = args[0] || {}
646 args[0].caller = '/tmp/script.js'
647 return instance[asJsonSym].apply(this, args)
648 }
649
650 return new Proxy(instance, { get })
651 }
652
653 const pretty = prettyFactory()
654 const log = traceCaller(pino({}, new Writable({
655 write (chunk, enc, cb) {
656 const formatted = pretty(chunk.toString())
657 t.is(
658 formatted,
659 `[${epoch}] INFO (${pid} on ${hostname}) </tmp/script.js>: foo\n`
660 )
661 cb()
662 }
663 })))
664 log.info('foo')
665 })
666
667 t.test('handles specified timestampKey', (t) => {
668 t.plan(1)
669 const pretty = prettyFactory({ timestampKey: '@timestamp' })
670 const arst = pretty(`{"msg":"hello world", "@timestamp":${epoch}, "level":30}`)
671 t.is(arst, `[${epoch}] INFO: hello world\n`)
672 })
673
674 t.test('keeps "v" key in log', (t) => {
675 t.plan(1)
676 const pretty = prettyFactory({ ignore: 'time' })
677 const log = pino({}, new Writable({
678 write (chunk, enc, cb) {
679 const formatted = pretty(chunk.toString())
680 t.is(formatted, `INFO (${pid} on ${hostname}):\n v: 1\n`)
681 cb()
682 }
683 }))
684 log.info({ v: 1 })
685 })
686
687 t.test('Hide object `{ key: "value" }` from output when flag `hideObject` is set', (t) => {
688 t.plan(1)
689 const pretty = prettyFactory({ hideObject: true })
690 const log = pino({}, new Writable({
691 write (chunk, enc, cb) {
692 const formatted = pretty(chunk.toString())
693 t.is(formatted, `[${epoch}] INFO (${pid} on ${hostname}): hello world\n`)
694 cb()
695 }
696 }))
697 log.info({ key: 'value' }, 'hello world')
698 })
699
700 t.test('Prints extra objects on one line with singleLine=true', (t) => {
701 t.plan(1)
702 const pretty = prettyFactory({
703 singleLine: true,
704 colorize: false,
705 customPrettifiers: {
706 upper: val => val.toUpperCase(),
707 undef: () => undefined
708 }
709 })
710 const log = pino({}, new Writable({
711 write (chunk, enc, cb) {
712 const formatted = pretty(chunk.toString())
713 t.is(formatted, `[${epoch}] INFO (${pid} on ${hostname}): message {"extra":{"foo":"bar","number":42},"upper":"FOOBAR"}\n`)
714
715 cb()
716 }
717 }))
718 log.info({ msg: 'message', extra: { foo: 'bar', number: 42 }, upper: 'foobar', undef: 'this will not show up' })
719 })
720
721 t.test('Does not print empty object with singleLine=true', (t) => {
722 t.plan(1)
723 const pretty = prettyFactory({ singleLine: true, colorize: false })
724 const log = pino({}, new Writable({
725 write (chunk, enc, cb) {
726 const formatted = pretty(chunk.toString())
727 t.is(formatted, `[${epoch}] INFO (${pid} on ${hostname}): message\n`)
728 cb()
729 }
730 }))
731 log.info({ msg: 'message' })
732 })
733
734 t.end()
735})