UNPKG

16 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('will format time to UTC', (t) => {
109 t.plan(1)
110 const pretty = prettyFactory({ translateTime: true })
111 const log = pino({}, new Writable({
112 write (chunk, enc, cb) {
113 const formatted = pretty(chunk.toString())
114 t.is(
115 formatted,
116 `[2018-03-30 17:35:28.992 +0000] INFO (${pid} on ${hostname}): foo\n`
117 )
118 cb()
119 }
120 }))
121 log.info('foo')
122 })
123
124 t.test('will format time to UTC in custom format', (t) => {
125 t.plan(1)
126 const pretty = prettyFactory({ translateTime: 'HH:MM:ss o' })
127 const log = pino({}, new Writable({
128 write (chunk, enc, cb) {
129 const formatted = pretty(chunk.toString())
130 const utcHour = dateformat(epoch, 'UTC:' + 'HH')
131 const offset = dateformat(epoch, 'UTC:' + 'o')
132 t.is(
133 formatted,
134 `[${utcHour}:35:28 ${offset}] INFO (${pid} on ${hostname}): foo\n`
135 )
136 cb()
137 }
138 }))
139 log.info('foo')
140 })
141
142 t.test('will format time to local systemzone in ISO 8601 format', (t) => {
143 t.plan(1)
144 const pretty = prettyFactory({ translateTime: 'sys:standard' })
145 const log = pino({}, new Writable({
146 write (chunk, enc, cb) {
147 const formatted = pretty(chunk.toString())
148 const localHour = dateformat(epoch, 'HH')
149 const localDate = dateformat(epoch, 'yyyy-mm-dd')
150 const offset = dateformat(epoch, 'o')
151 t.is(
152 formatted,
153 `[${localDate} ${localHour}:35:28.992 ${offset}] INFO (${pid} on ${hostname}): foo\n`
154 )
155 cb()
156 }
157 }))
158 log.info('foo')
159 })
160
161 t.test('will format time to local systemzone in custom format', (t) => {
162 t.plan(1)
163 const pretty = prettyFactory({
164 translateTime: 'SYS:yyyy/mm/dd HH:MM:ss o'
165 })
166 const log = pino({}, new Writable({
167 write (chunk, enc, cb) {
168 const formatted = pretty(chunk.toString())
169 const localHour = dateformat(epoch, 'HH')
170 const localDate = dateformat(epoch, 'yyyy/mm/dd')
171 const offset = dateformat(epoch, 'o')
172 t.is(
173 formatted,
174 `[${localDate} ${localHour}:35:28 ${offset}] INFO (${pid} on ${hostname}): foo\n`
175 )
176 cb()
177 }
178 }))
179 log.info('foo')
180 })
181
182 // TODO: 2019-03-30 -- We don't really want the indentation in this case? Or at least some better formatting.
183 t.test('handles missing time', (t) => {
184 t.plan(1)
185 const pretty = prettyFactory()
186 const formatted = pretty('{"hello":"world"}')
187 t.is(formatted, ' hello: "world"\n')
188 })
189
190 t.test('handles missing pid, hostname and name', (t) => {
191 t.plan(1)
192 const pretty = prettyFactory()
193 const log = pino({ base: null }, new Writable({
194 write (chunk, enc, cb) {
195 const formatted = pretty(chunk.toString())
196 t.match(formatted, /\[.*\] INFO : hello world/)
197 cb()
198 }
199 }))
200 log.info('hello world')
201 })
202
203 t.test('handles missing pid', (t) => {
204 t.plan(1)
205 const pretty = prettyFactory()
206 const name = 'test'
207 const msg = 'hello world'
208 const regex = new RegExp('\\[.*\\] INFO \\(' + name + ' on ' + hostname + '\\): ' + msg)
209
210 const opts = {
211 base: {
212 name: name,
213 hostname: hostname
214 }
215 }
216 const log = pino(opts, new Writable({
217 write (chunk, enc, cb) {
218 const formatted = pretty(chunk.toString())
219 t.match(formatted, regex)
220 cb()
221 }
222 }))
223
224 log.info(msg)
225 })
226
227 t.test('handles missing hostname', (t) => {
228 t.plan(1)
229 const pretty = prettyFactory()
230 const name = 'test'
231 const msg = 'hello world'
232 const regex = new RegExp('\\[.*\\] INFO \\(' + name + '/' + pid + '\\): ' + msg)
233
234 const opts = {
235 base: {
236 name: name,
237 pid: process.pid
238 }
239 }
240 const log = pino(opts, new Writable({
241 write (chunk, enc, cb) {
242 const formatted = pretty(chunk.toString())
243 t.match(formatted, regex)
244 cb()
245 }
246 }))
247
248 log.info(msg)
249 })
250
251 t.test('handles missing name', (t) => {
252 t.plan(1)
253 const pretty = prettyFactory()
254 const msg = 'hello world'
255 const regex = new RegExp('\\[.*\\] INFO \\(' + process.pid + ' on ' + hostname + '\\): ' + msg)
256
257 const opts = {
258 base: {
259 hostname: hostname,
260 pid: process.pid
261 }
262 }
263 const log = pino(opts, new Writable({
264 write (chunk, enc, cb) {
265 const formatted = pretty(chunk.toString())
266 t.match(formatted, regex)
267 cb()
268 }
269 }))
270
271 log.info(msg)
272 })
273
274 t.test('works without time', (t) => {
275 t.plan(1)
276 const pretty = prettyFactory()
277 const log = pino({ timestamp: null }, new Writable({
278 write (chunk, enc, cb) {
279 const formatted = pretty(chunk.toString())
280 t.is(formatted, `INFO (${pid} on ${hostname}): hello world\n`)
281 cb()
282 }
283 }))
284 log.info('hello world')
285 })
286
287 t.test('prettifies properties', (t) => {
288 t.plan(1)
289 const pretty = prettyFactory()
290 const log = pino({}, new Writable({
291 write (chunk, enc, cb) {
292 const formatted = pretty(chunk.toString())
293 t.match(formatted, ' a: "b"')
294 cb()
295 }
296 }))
297 log.info({ a: 'b' }, 'hello world')
298 })
299
300 t.test('prettifies nested properties', (t) => {
301 t.plan(6)
302 const expectedLines = [
303 ' a: {',
304 ' "b": {',
305 ' "c": "d"',
306 ' }',
307 ' }'
308 ]
309 const pretty = prettyFactory()
310 const log = pino({}, new Writable({
311 write (chunk, enc, cb) {
312 const formatted = pretty(chunk.toString())
313 const lines = formatted.split('\n')
314 t.is(lines.length, expectedLines.length + 2)
315 lines.shift(); lines.pop()
316 for (var i = 0; i < lines.length; i += 1) {
317 t.is(lines[i], expectedLines[i])
318 }
319 cb()
320 }
321 }))
322 log.info({ a: { b: { c: 'd' } } }, 'hello world')
323 })
324
325 t.test('treats the name with care', (t) => {
326 t.plan(1)
327 const pretty = prettyFactory()
328 const log = pino({ name: 'matteo' }, new Writable({
329 write (chunk, enc, cb) {
330 const formatted = pretty(chunk.toString())
331 t.is(formatted, `[${epoch}] INFO (matteo/${pid} on ${hostname}): hello world\n`)
332 cb()
333 }
334 }))
335 log.info('hello world')
336 })
337
338 t.test('handles spec allowed primitives', (t) => {
339 const pretty = prettyFactory()
340 let formatted = pretty(null)
341 t.is(formatted, 'null\n')
342
343 formatted = pretty(true)
344 t.is(formatted, 'true\n')
345
346 formatted = pretty(false)
347 t.is(formatted, 'false\n')
348
349 t.end()
350 })
351
352 t.test('handles numbers', (t) => {
353 const pretty = prettyFactory()
354 let formatted = pretty(2)
355 t.is(formatted, '2\n')
356
357 formatted = pretty(-2)
358 t.is(formatted, '-2\n')
359
360 formatted = pretty(0.2)
361 t.is(formatted, '0.2\n')
362
363 formatted = pretty(Infinity)
364 t.is(formatted, 'Infinity\n')
365
366 formatted = pretty(NaN)
367 t.is(formatted, 'NaN\n')
368
369 t.end()
370 })
371
372 t.test('handles `undefined` input', (t) => {
373 t.plan(1)
374 const pretty = prettyFactory()
375 const formatted = pretty(undefined)
376 t.is(formatted, 'undefined\n')
377 })
378
379 t.test('handles customLogLevel', (t) => {
380 t.plan(1)
381 const pretty = prettyFactory()
382 const log = pino({ customLevels: { testCustom: 35 } }, new Writable({
383 write (chunk, enc, cb) {
384 const formatted = pretty(chunk.toString())
385 t.match(formatted, /USERLVL/)
386 cb()
387 }
388 }))
389 log.testCustom('test message')
390 })
391
392 t.test('supports pino metadata API', (t) => {
393 t.plan(1)
394 const dest = new Writable({
395 write (chunk, enc, cb) {
396 t.is(
397 chunk.toString(),
398 `[${epoch}] INFO (${pid} on ${hostname}): foo\n`
399 )
400 cb()
401 }
402 })
403 const log = pino({
404 prettifier: prettyFactory,
405 prettyPrint: true
406 }, dest)
407 log.info('foo')
408 })
409
410 t.test('can swap date and level position through meta stream', (t) => {
411 t.plan(1)
412
413 const dest = new Writable({
414 objectMode: true,
415 write (formatted, enc, cb) {
416 t.is(
417 formatted,
418 `INFO [${epoch}] (${pid} on ${hostname}): foo\n`
419 )
420 cb()
421 }
422 })
423 const log = pino({
424 prettifier: prettyFactory,
425 prettyPrint: {
426 levelFirst: true
427 }
428 }, dest)
429 log.info('foo')
430 })
431
432 t.test('filter some lines based on jmespath', (t) => {
433 t.plan(3)
434 const pretty = prettyFactory({ search: 'foo.bar' })
435 const expected = [
436 undefined,
437 undefined,
438 `[${epoch}] INFO (${pid} on ${hostname}): foo\n foo: {\n "bar": true\n }\n`
439 ]
440 const log = pino({}, new Writable({
441 write (chunk, enc, cb) {
442 const formatted = pretty(chunk.toString())
443 t.is(
444 formatted,
445 expected.shift()
446 )
447 cb()
448 }
449 }))
450 log.info('foo')
451 log.info({ something: 'else' }, 'foo')
452 // only this line will be formatted
453 log.info({ foo: { bar: true } }, 'foo')
454 })
455
456 t.test('handles `undefined` return values', (t) => {
457 t.plan(2)
458 const pretty = prettyFactory({ search: 'msg == \'hello world\'' })
459 let formatted = pretty(`{"msg":"nope", "time":${epoch}, "level":30, "v":1}`)
460 t.is(formatted, undefined)
461 formatted = pretty(`{"msg":"hello world", "time":${epoch}, "level":30, "v":1}`)
462 t.is(formatted, `[${epoch}] INFO : hello world\n`)
463 })
464
465 t.test('formats a line with an undefined field', (t) => {
466 t.plan(1)
467 const pretty = prettyFactory()
468 const log = pino({}, new Writable({
469 write (chunk, enc, cb) {
470 const obj = JSON.parse(chunk.toString())
471 // weird hack, but we should not crash
472 obj.a = undefined
473 const formatted = pretty(obj)
474 t.is(
475 formatted,
476 `[${epoch}] INFO (${pid} on ${hostname}): foo\n`
477 )
478 cb()
479 }
480 }))
481 log.info('foo')
482 })
483
484 t.test('prettifies msg object', (t) => {
485 t.plan(6)
486 const expectedLines = [
487 ' msg: {',
488 ' "b": {',
489 ' "c": "d"',
490 ' }',
491 ' }'
492 ]
493 const pretty = prettyFactory()
494 const log = pino({}, new Writable({
495 write (chunk, enc, cb) {
496 const formatted = pretty(chunk.toString())
497 const lines = formatted.split('\n')
498 t.is(lines.length, expectedLines.length + 2)
499 lines.shift(); lines.pop()
500 for (var i = 0; i < lines.length; i += 1) {
501 t.is(lines[i], expectedLines[i])
502 }
503 cb()
504 }
505 }))
506 log.info({ msg: { b: { c: 'd' } } })
507 })
508
509 t.test('prettifies msg object with circular references', (t) => {
510 t.plan(7)
511 const expectedLines = [
512 ' msg: {',
513 ' "b": {',
514 ' "c": "d"',
515 ' },',
516 ' "a": "[Circular]"',
517 ' }'
518 ]
519 const pretty = prettyFactory()
520 const log = pino({}, new Writable({
521 write (chunk, enc, cb) {
522 const formatted = pretty(chunk.toString())
523 const lines = formatted.split('\n')
524 t.is(lines.length, expectedLines.length + 2)
525 lines.shift(); lines.pop()
526 for (var i = 0; i < lines.length; i += 1) {
527 t.is(lines[i], expectedLines[i])
528 }
529 cb()
530 }
531 }))
532
533 const msg = { b: { c: 'd' } }
534 msg.a = msg
535 log.info({ msg })
536 })
537
538 t.test('prettifies custom key', (t) => {
539 t.plan(1)
540 const pretty = prettyFactory({
541 customPrettifiers: {
542 foo: val => `${val}_baz\nmultiline`,
543 cow: val => val.toUpperCase()
544 }
545 })
546 const arst = pretty('{"msg":"hello world", "foo": "bar", "cow": "moo", "level":30, "v":1}')
547 t.is(arst, 'INFO : hello world\n foo: bar_baz\n multiline\n cow: MOO\n')
548 })
549
550 t.test('does not prettify custom key that does not exists', (t) => {
551 t.plan(1)
552 const pretty = prettyFactory({
553 customPrettifiers: {
554 foo: val => `${val}_baz`,
555 cow: val => val.toUpperCase()
556 }
557 })
558 const arst = pretty('{"msg":"hello world", "foo": "bar", "level":30, "v":1}')
559 t.is(arst, 'INFO : hello world\n foo: bar_baz\n')
560 })
561
562 t.test('prettifies object with some undefined values', (t) => {
563 t.plan(1)
564 const dest = new Writable({
565 write (chunk, _, cb) {
566 t.is(
567 chunk + '',
568 `[${epoch}] INFO (${pid} on ${hostname}):\n a: {\n "b": "c"\n }\n n: null\n`
569 )
570 cb()
571 }
572 })
573 const log = pino({
574 prettifier: prettyFactory,
575 prettyPrint: true
576 }, dest)
577 log.info({
578 a: { b: 'c' },
579 s: Symbol.for('s'),
580 f: f => f,
581 c: class C {},
582 n: null,
583 err: { toJSON () {} }
584 })
585 })
586
587 t.test('ignores multiple keys', (t) => {
588 t.plan(1)
589 const pretty = prettyFactory({ ignore: 'pid,hostname' })
590 const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30, "v":1}`)
591 t.is(arst, `[${epoch}] INFO : hello world\n`)
592 })
593
594 t.test('ignores a single key', (t) => {
595 t.plan(1)
596 const pretty = prettyFactory({ ignore: 'pid' })
597 const arst = pretty(`{"msg":"hello world", "pid":"${pid}", "hostname":"${hostname}", "time":${epoch}, "level":30, "v":1}`)
598 t.is(arst, `[${epoch}] INFO (on ${hostname}): hello world\n`)
599 })
600
601 t.end()
602})