fastify 4.15.0 → 4.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +3 -3
  2. package/docs/Guides/Database.md +7 -8
  3. package/docs/Guides/Ecosystem.md +16 -7
  4. package/docs/Guides/Getting-Started.md +1 -1
  5. package/docs/Guides/Migration-Guide-V4.md +21 -0
  6. package/docs/Guides/Plugins-Guide.md +1 -1
  7. package/docs/Guides/Prototype-Poisoning.md +31 -39
  8. package/docs/Guides/Recommendations.md +1 -1
  9. package/docs/Guides/Write-Type-Provider.md +3 -3
  10. package/docs/Reference/Hooks.md +42 -9
  11. package/docs/Reference/Reply.md +2 -2
  12. package/docs/Reference/Routes.md +13 -2
  13. package/docs/Reference/Server.md +19 -3
  14. package/docs/Reference/Type-Providers.md +1 -1
  15. package/docs/Reference/TypeScript.md +3 -3
  16. package/docs/index.md +2 -2
  17. package/examples/benchmark/parser.js +47 -0
  18. package/fastify.js +26 -23
  19. package/lib/error-serializer.js +9 -162
  20. package/lib/hooks.js +3 -0
  21. package/lib/server.js +9 -4
  22. package/lib/validation.js +10 -8
  23. package/lib/warnings.js +2 -0
  24. package/package.json +7 -6
  25. package/test/close.test.js +91 -0
  26. package/test/route-hooks.test.js +29 -0
  27. package/test/route.test.js +1 -1
  28. package/test/schema-feature.test.js +128 -35
  29. package/test/serial/logger.0.test.js +861 -0
  30. package/test/serial/logger.1.test.js +862 -0
  31. package/test/serial/tap-parallel-not-ok +0 -0
  32. package/test/server.test.js +10 -0
  33. package/test/types/hooks.test-d.ts +66 -11
  34. package/test/types/import.js +1 -1
  35. package/test/types/instance.test-d.ts +2 -0
  36. package/test/types/route.test-d.ts +106 -5
  37. package/test/types/type-provider.test-d.ts +77 -10
  38. package/types/hooks.d.ts +28 -0
  39. package/types/instance.d.ts +20 -1
  40. package/types/logger.d.ts +1 -1
  41. package/types/route.d.ts +41 -11
  42. package/test/logger.test.js +0 -1721
@@ -0,0 +1,862 @@
1
+ 'use strict'
2
+
3
+ const http = require('http')
4
+ const stream = require('stream')
5
+ const os = require('os')
6
+ const fs = require('fs')
7
+
8
+ const t = require('tap')
9
+ const split = require('split2')
10
+ const pino = require('pino')
11
+ const path = require('path')
12
+ const { streamSym } = require('pino/lib/symbols')
13
+
14
+ const Fastify = require('../../fastify')
15
+ const helper = require('../helper')
16
+ const { once, on } = stream
17
+
18
+ function createDeferredPromise () {
19
+ const promise = {}
20
+ promise.promise = new Promise(function (resolve) {
21
+ promise.resolve = resolve
22
+ })
23
+ return promise
24
+ }
25
+
26
+ let count = 0
27
+ function createTempFile () {
28
+ const file = path.join(os.tmpdir(), `sonic-boom-${process.pid}-${process.hrtime().toString()}-${count++}`)
29
+ function cleanup () {
30
+ try {
31
+ fs.unlinkSync(file)
32
+ } catch { }
33
+ }
34
+ return { file, cleanup }
35
+ }
36
+
37
+ function request (url, cleanup = () => { }) {
38
+ const promise = createDeferredPromise()
39
+ http.get(url, (res) => {
40
+ const chunks = []
41
+ // we consume the response
42
+ res.on('data', function (chunk) {
43
+ chunks.push(chunk)
44
+ })
45
+ res.once('end', function () {
46
+ cleanup(res, Buffer.concat(chunks).toString())
47
+ promise.resolve()
48
+ })
49
+ })
50
+ return promise.promise
51
+ }
52
+
53
+ t.test('test log stream', (t) => {
54
+ t.setTimeout(60000)
55
+
56
+ let localhost
57
+ let localhostForURL
58
+
59
+ t.plan(24)
60
+
61
+ t.before(async function () {
62
+ [localhost, localhostForURL] = await helper.getLoopbackHost()
63
+ })
64
+
65
+ t.test('Should use serializers from plugin and route', async (t) => {
66
+ const lines = [
67
+ { msg: 'incoming request' },
68
+ { test: 'XHello', test2: 'ZHello' },
69
+ { msg: 'request completed' }
70
+ ]
71
+ t.plan(lines.length + 1)
72
+
73
+ const stream = split(JSON.parse)
74
+
75
+ const logger = pino({ level: 'info' }, stream)
76
+ const fastify = Fastify({
77
+ logger
78
+ })
79
+ t.teardown(fastify.close.bind(fastify))
80
+
81
+ fastify.register(context1, {
82
+ logSerializers: { test: value => 'X' + value }
83
+ })
84
+
85
+ function context1 (instance, opts, done) {
86
+ instance.get('/', {
87
+ logSerializers: {
88
+ test2: value => 'Z' + value
89
+ }
90
+ }, (req, reply) => {
91
+ req.log.info({ test: 'Hello', test2: 'Hello' }) // { test: 'XHello', test2: 'ZHello' }
92
+ reply.send({ hello: 'world' })
93
+ })
94
+ done()
95
+ }
96
+
97
+ await fastify.ready()
98
+
99
+ {
100
+ const response = await fastify.inject({ method: 'GET', url: '/' })
101
+ const body = await response.json()
102
+ t.same(body, { hello: 'world' })
103
+ }
104
+
105
+ for await (const [line] of on(stream, 'data')) {
106
+ t.match(line, lines.shift())
107
+ if (lines.length === 0) break
108
+ }
109
+ })
110
+
111
+ t.test('Should use serializers from instance fastify and route', async (t) => {
112
+ const lines = [
113
+ { msg: 'incoming request' },
114
+ { test: 'XHello', test2: 'ZHello' },
115
+ { msg: 'request completed' }
116
+ ]
117
+ t.plan(lines.length + 1)
118
+
119
+ const stream = split(JSON.parse)
120
+
121
+ const logger = pino({
122
+ level: 'info',
123
+ serializers: {
124
+ test: value => 'X' + value,
125
+ test2: value => 'This should be override - ' + value
126
+ }
127
+ }, stream)
128
+ const fastify = Fastify({
129
+ logger
130
+ })
131
+ t.teardown(fastify.close.bind(fastify))
132
+
133
+ fastify.get('/', {
134
+ logSerializers: {
135
+ test2: value => 'Z' + value
136
+ }
137
+ }, (req, reply) => {
138
+ req.log.info({ test: 'Hello', test2: 'Hello' }) // { test: 'XHello', test2: 'ZHello' }
139
+ reply.send({ hello: 'world' })
140
+ })
141
+
142
+ await fastify.ready()
143
+
144
+ {
145
+ const response = await fastify.inject({ method: 'GET', url: '/' })
146
+ const body = await response.json()
147
+ t.same(body, { hello: 'world' })
148
+ }
149
+
150
+ for await (const [line] of on(stream, 'data')) {
151
+ t.match(line, lines.shift())
152
+ if (lines.length === 0) break
153
+ }
154
+ })
155
+
156
+ t.test('Should use serializers inherit from contexts', async (t) => {
157
+ const lines = [
158
+ { msg: 'incoming request' },
159
+ { test: 'XHello', test2: 'YHello', test3: 'ZHello' },
160
+ { msg: 'request completed' }
161
+ ]
162
+ t.plan(lines.length + 1)
163
+
164
+ const stream = split(JSON.parse)
165
+
166
+ const logger = pino({
167
+ level: 'info',
168
+ serializers: {
169
+ test: value => 'X' + value
170
+ }
171
+ }, stream)
172
+
173
+ const fastify = Fastify({ logger })
174
+ t.teardown(fastify.close.bind(fastify))
175
+
176
+ fastify.register(context1, { logSerializers: { test2: value => 'Y' + value } })
177
+
178
+ function context1 (instance, opts, done) {
179
+ instance.get('/', {
180
+ logSerializers: {
181
+ test3: value => 'Z' + value
182
+ }
183
+ }, (req, reply) => {
184
+ req.log.info({ test: 'Hello', test2: 'Hello', test3: 'Hello' }) // { test: 'XHello', test2: 'YHello', test3: 'ZHello' }
185
+ reply.send({ hello: 'world' })
186
+ })
187
+ done()
188
+ }
189
+
190
+ await fastify.ready()
191
+
192
+ {
193
+ const response = await fastify.inject({ method: 'GET', url: '/' })
194
+ const body = await response.json()
195
+ t.same(body, { hello: 'world' })
196
+ }
197
+
198
+ for await (const [line] of on(stream, 'data')) {
199
+ t.match(line, lines.shift())
200
+ if (lines.length === 0) break
201
+ }
202
+ })
203
+
204
+ t.test('Should increase the log level for a specific plugin', async (t) => {
205
+ const lines = ['Hello']
206
+ t.plan(lines.length * 2 + 1)
207
+
208
+ const stream = split(JSON.parse)
209
+
210
+ const logger = pino({ level: 'info' }, stream)
211
+
212
+ const fastify = Fastify({
213
+ logger
214
+ })
215
+ t.teardown(fastify.close.bind(fastify))
216
+
217
+ fastify.register(function (instance, opts, done) {
218
+ instance.get('/', (req, reply) => {
219
+ req.log.error('Hello') // we should see this log
220
+ reply.send({ hello: 'world' })
221
+ })
222
+ done()
223
+ }, { logLevel: 'error' })
224
+
225
+ await fastify.ready()
226
+
227
+ {
228
+ const response = await fastify.inject({ method: 'GET', url: '/' })
229
+ const body = await response.json()
230
+ t.same(body, { hello: 'world' })
231
+ }
232
+
233
+ for await (const [line] of on(stream, 'data')) {
234
+ t.equal(line.level, 50)
235
+ t.equal(line.msg, lines.shift())
236
+ if (lines.length === 0) break
237
+ }
238
+ })
239
+
240
+ t.test('Should set the log level for the customized 404 handler', async (t) => {
241
+ const lines = ['Hello']
242
+ t.plan(lines.length * 2 + 1)
243
+
244
+ const stream = split(JSON.parse)
245
+
246
+ const logger = pino({ level: 'warn' }, stream)
247
+
248
+ const fastify = Fastify({
249
+ logger
250
+ })
251
+ t.teardown(fastify.close.bind(fastify))
252
+
253
+ fastify.register(function (instance, opts, done) {
254
+ instance.setNotFoundHandler(function (req, reply) {
255
+ req.log.error('Hello')
256
+ reply.code(404).send()
257
+ })
258
+ done()
259
+ }, { logLevel: 'error' })
260
+
261
+ await fastify.ready()
262
+
263
+ {
264
+ const response = await fastify.inject({ method: 'GET', url: '/' })
265
+ t.equal(response.statusCode, 404)
266
+ }
267
+
268
+ for await (const [line] of on(stream, 'data')) {
269
+ t.equal(line.level, 50)
270
+ t.equal(line.msg, lines.shift())
271
+ if (lines.length === 0) break
272
+ }
273
+ })
274
+
275
+ t.test('Should set the log level for the customized 500 handler', async (t) => {
276
+ const lines = ['Hello']
277
+ t.plan(lines.length * 2 + 1)
278
+
279
+ const stream = split(JSON.parse)
280
+
281
+ const logger = pino({ level: 'warn' }, stream)
282
+
283
+ const fastify = Fastify({
284
+ logger
285
+ })
286
+ t.teardown(fastify.close.bind(fastify))
287
+
288
+ fastify.register(function (instance, opts, done) {
289
+ instance.get('/', (req, reply) => {
290
+ req.log.error('kaboom')
291
+ reply.send(new Error('kaboom'))
292
+ })
293
+
294
+ instance.setErrorHandler(function (e, request, reply) {
295
+ reply.log.fatal('Hello')
296
+ reply.code(500).send()
297
+ })
298
+ done()
299
+ }, { logLevel: 'fatal' })
300
+
301
+ await fastify.ready()
302
+
303
+ {
304
+ const response = await fastify.inject({ method: 'GET', url: '/' })
305
+ t.equal(response.statusCode, 500)
306
+ }
307
+
308
+ for await (const [line] of on(stream, 'data')) {
309
+ t.equal(line.level, 60)
310
+ t.equal(line.msg, lines.shift())
311
+ if (lines.length === 0) break
312
+ }
313
+ })
314
+
315
+ t.test('Should set a custom log level for a specific route', async (t) => {
316
+ const lines = ['incoming request', 'Hello', 'request completed']
317
+ t.plan(lines.length + 2)
318
+
319
+ const stream = split(JSON.parse)
320
+
321
+ const logger = pino({ level: 'error' }, stream)
322
+
323
+ const fastify = Fastify({
324
+ logger
325
+ })
326
+ t.teardown(fastify.close.bind(fastify))
327
+
328
+ fastify.get('/log', { logLevel: 'info' }, (req, reply) => {
329
+ req.log.info('Hello')
330
+ reply.send({ hello: 'world' })
331
+ })
332
+
333
+ fastify.get('/no-log', (req, reply) => {
334
+ req.log.info('Hello')
335
+ reply.send({ hello: 'world' })
336
+ })
337
+
338
+ await fastify.ready()
339
+
340
+ {
341
+ const response = await fastify.inject({ method: 'GET', url: '/log' })
342
+ const body = await response.json()
343
+ t.same(body, { hello: 'world' })
344
+ }
345
+
346
+ {
347
+ const response = await fastify.inject({ method: 'GET', url: '/no-log' })
348
+ const body = await response.json()
349
+ t.same(body, { hello: 'world' })
350
+ }
351
+
352
+ for await (const [line] of on(stream, 'data')) {
353
+ t.equal(line.msg, lines.shift())
354
+ if (lines.length === 0) break
355
+ }
356
+ })
357
+
358
+ t.test('The default 404 handler logs the incoming request', async (t) => {
359
+ const lines = ['incoming request', 'Route GET:/not-found not found', 'request completed']
360
+ t.plan(lines.length + 1)
361
+
362
+ const stream = split(JSON.parse)
363
+
364
+ const logger = pino({ level: 'trace' }, stream)
365
+
366
+ const fastify = Fastify({
367
+ logger
368
+ })
369
+ t.teardown(fastify.close.bind(fastify))
370
+
371
+ await fastify.ready()
372
+
373
+ {
374
+ const response = await fastify.inject({ method: 'GET', url: '/not-found' })
375
+ t.equal(response.statusCode, 404)
376
+ }
377
+
378
+ for await (const [line] of on(stream, 'data')) {
379
+ t.equal(line.msg, lines.shift())
380
+ if (lines.length === 0) break
381
+ }
382
+ })
383
+
384
+ t.test('should serialize request and response', async (t) => {
385
+ const lines = [
386
+ { req: { method: 'GET', url: '/500' }, msg: 'incoming request' },
387
+ { req: { method: 'GET', url: '/500' }, msg: '500 error' },
388
+ { msg: 'request completed' }
389
+ ]
390
+ t.plan(lines.length + 1)
391
+
392
+ const stream = split(JSON.parse)
393
+ const fastify = Fastify({ logger: { level: 'info', stream } })
394
+ t.teardown(fastify.close.bind(fastify))
395
+
396
+ fastify.get('/500', (req, reply) => {
397
+ reply.code(500).send(Error('500 error'))
398
+ })
399
+
400
+ await fastify.ready()
401
+
402
+ {
403
+ const response = await fastify.inject({ method: 'GET', url: '/500' })
404
+ t.equal(response.statusCode, 500)
405
+ }
406
+
407
+ for await (const [line] of on(stream, 'data')) {
408
+ t.match(line, lines.shift())
409
+ if (lines.length === 0) break
410
+ }
411
+ })
412
+
413
+ t.test('Wrap IPv6 address in listening log message', async (t) => {
414
+ t.plan(1)
415
+
416
+ const interfaces = os.networkInterfaces()
417
+ const ipv6 = Object.keys(interfaces)
418
+ .filter(name => name.substr(0, 2) === 'lo')
419
+ .map(name => interfaces[name])
420
+ .reduce((list, set) => list.concat(set), [])
421
+ .filter(info => info.family === 'IPv6')
422
+ .map(info => info.address)
423
+ .shift()
424
+
425
+ if (ipv6 === undefined) {
426
+ t.pass('No IPv6 loopback interface')
427
+ } else {
428
+ const stream = split(JSON.parse)
429
+ const fastify = Fastify({
430
+ logger: {
431
+ stream,
432
+ level: 'info'
433
+ }
434
+ })
435
+ t.teardown(fastify.close.bind(fastify))
436
+
437
+ await fastify.ready()
438
+ await fastify.listen({ port: 0, host: ipv6 })
439
+
440
+ {
441
+ const [line] = await once(stream, 'data')
442
+ t.same(line.msg, `Server listening at http://[${ipv6}]:${fastify.server.address().port}`)
443
+ }
444
+ }
445
+ })
446
+
447
+ t.test('Do not wrap IPv4 address', async (t) => {
448
+ t.plan(1)
449
+ const stream = split(JSON.parse)
450
+ const fastify = Fastify({
451
+ logger: {
452
+ stream,
453
+ level: 'info'
454
+ }
455
+ })
456
+ t.teardown(fastify.close.bind(fastify))
457
+
458
+ await fastify.ready()
459
+ await fastify.listen({ port: 0, host: '127.0.0.1' })
460
+
461
+ {
462
+ const [line] = await once(stream, 'data')
463
+ t.same(line.msg, `Server listening at http://127.0.0.1:${fastify.server.address().port}`)
464
+ }
465
+ })
466
+
467
+ t.test('file option', async (t) => {
468
+ const lines = [
469
+ { msg: /Server listening at/ },
470
+ { reqId: /req-/, req: { method: 'GET', url: '/' }, msg: 'incoming request' },
471
+ { reqId: /req-/, res: { statusCode: 200 }, msg: 'request completed' }
472
+ ]
473
+ t.plan(lines.length + 3)
474
+ const { file, cleanup } = createTempFile(t)
475
+
476
+ const fastify = Fastify({
477
+ logger: { file }
478
+ })
479
+ t.teardown(() => {
480
+ // cleanup the file after sonic-boom closed
481
+ // otherwise we may face racing condition
482
+ fastify.log[streamSym].once('close', cleanup)
483
+ // we must flush the stream ourself
484
+ // otherwise buffer may whole sonic-boom
485
+ fastify.log[streamSym].flushSync()
486
+ // end after flushing to actually close file
487
+ fastify.log[streamSym].end()
488
+ })
489
+ t.teardown(fastify.close.bind(fastify))
490
+
491
+ fastify.get('/', function (req, reply) {
492
+ t.ok(req.log)
493
+ reply.send({ hello: 'world' })
494
+ })
495
+
496
+ await fastify.ready()
497
+ await fastify.listen({ port: 0, host: localhost })
498
+
499
+ await request(`http://${localhostForURL}:` + fastify.server.address().port)
500
+
501
+ // we already own the full log
502
+ const stream = fs.createReadStream(file).pipe(split(JSON.parse))
503
+ t.teardown(stream.resume.bind(stream))
504
+
505
+ let id
506
+ for await (const [line] of on(stream, 'data')) {
507
+ if (id === undefined && line.reqId) id = line.reqId
508
+ if (id !== undefined && line.reqId) t.equal(line.reqId, id)
509
+ t.match(line, lines.shift())
510
+ if (lines.length === 0) break
511
+ }
512
+ })
513
+
514
+ t.test('should log the error if no error handler is defined', async (t) => {
515
+ const lines = [
516
+ { msg: /Server listening at/ },
517
+ { msg: 'incoming request' },
518
+ { level: 50, msg: 'a generic error' },
519
+ { res: { statusCode: 500 }, msg: 'request completed' }
520
+ ]
521
+ t.plan(lines.length + 1)
522
+
523
+ const stream = split(JSON.parse)
524
+ const fastify = Fastify({
525
+ logger: {
526
+ stream,
527
+ level: 'info'
528
+ }
529
+ })
530
+ t.teardown(fastify.close.bind(fastify))
531
+
532
+ fastify.get('/error', function (req, reply) {
533
+ t.ok(req.log)
534
+ reply.send(new Error('a generic error'))
535
+ })
536
+
537
+ await fastify.ready()
538
+ await fastify.listen({ port: 0, host: localhost })
539
+
540
+ await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error')
541
+
542
+ for await (const [line] of on(stream, 'data')) {
543
+ t.match(line, lines.shift())
544
+ if (lines.length === 0) break
545
+ }
546
+ })
547
+
548
+ t.test('should log as info if error status code >= 400 and < 500 if no error handler is defined', async (t) => {
549
+ const lines = [
550
+ { msg: /Server listening at/ },
551
+ { msg: 'incoming request' },
552
+ { level: 30, msg: 'a 400 error' },
553
+ { res: { statusCode: 400 }, msg: 'request completed' }
554
+ ]
555
+ t.plan(lines.length + 1)
556
+ const stream = split(JSON.parse)
557
+ const fastify = Fastify({
558
+ logger: {
559
+ stream,
560
+ level: 'info'
561
+ }
562
+ })
563
+ t.teardown(fastify.close.bind(fastify))
564
+
565
+ fastify.get('/400', function (req, reply) {
566
+ t.ok(req.log)
567
+ reply.send(Object.assign(new Error('a 400 error'), { statusCode: 400 }))
568
+ })
569
+ fastify.get('/503', function (req, reply) {
570
+ t.ok(req.log)
571
+ reply.send(Object.assign(new Error('a 503 error'), { statusCode: 503 }))
572
+ })
573
+
574
+ await fastify.ready()
575
+ await fastify.listen({ port: 0, host: localhost })
576
+
577
+ await request(`http://${localhostForURL}:` + fastify.server.address().port + '/400')
578
+
579
+ for await (const [line] of on(stream, 'data')) {
580
+ t.match(line, lines.shift())
581
+ if (lines.length === 0) break
582
+ }
583
+ })
584
+
585
+ t.test('should log as error if error status code >= 500 if no error handler is defined', async (t) => {
586
+ const lines = [
587
+ { msg: /Server listening at/ },
588
+ { msg: 'incoming request' },
589
+ { level: 50, msg: 'a 503 error' },
590
+ { res: { statusCode: 503 }, msg: 'request completed' }
591
+ ]
592
+ t.plan(lines.length + 1)
593
+ const stream = split(JSON.parse)
594
+ const fastify = Fastify({
595
+ logger: {
596
+ stream,
597
+ level: 'info'
598
+ }
599
+ })
600
+ t.teardown(fastify.close.bind(fastify))
601
+ fastify.get('/503', function (req, reply) {
602
+ t.ok(req.log)
603
+ reply.send(Object.assign(new Error('a 503 error'), { statusCode: 503 }))
604
+ })
605
+
606
+ await fastify.ready()
607
+ await fastify.listen({ port: 0, host: localhost })
608
+
609
+ await request(`http://${localhostForURL}:` + fastify.server.address().port + '/503')
610
+
611
+ for await (const [line] of on(stream, 'data')) {
612
+ t.match(line, lines.shift())
613
+ if (lines.length === 0) break
614
+ }
615
+ })
616
+
617
+ t.test('should not log the error if error handler is defined and it does not error', async (t) => {
618
+ const lines = [
619
+ { msg: /Server listening at/ },
620
+ { level: 30, msg: 'incoming request' },
621
+ { res: { statusCode: 200 }, msg: 'request completed' }
622
+ ]
623
+ t.plan(lines.length + 2)
624
+ const stream = split(JSON.parse)
625
+ const fastify = Fastify({
626
+ logger: {
627
+ stream,
628
+ level: 'info'
629
+ }
630
+ })
631
+ t.teardown(fastify.close.bind(fastify))
632
+ fastify.get('/error', function (req, reply) {
633
+ t.ok(req.log)
634
+ reply.send(new Error('something happened'))
635
+ })
636
+ fastify.setErrorHandler((err, req, reply) => {
637
+ t.ok(err)
638
+ reply.send('something bad happened')
639
+ })
640
+
641
+ await fastify.ready()
642
+ await fastify.listen({ port: 0, host: localhost })
643
+
644
+ await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error')
645
+
646
+ for await (const [line] of on(stream, 'data')) {
647
+ t.match(line, lines.shift())
648
+ if (lines.length === 0) break
649
+ }
650
+ })
651
+
652
+ t.test('should not rely on raw request to log errors', async (t) => {
653
+ const lines = [
654
+ { msg: /Server listening at/ },
655
+ { level: 30, msg: 'incoming request' },
656
+ { res: { statusCode: 415 }, msg: 'something happened' },
657
+ { res: { statusCode: 415 }, msg: 'request completed' }
658
+ ]
659
+ t.plan(lines.length + 1)
660
+ const stream = split(JSON.parse)
661
+ const fastify = Fastify({
662
+ logger: {
663
+ stream,
664
+ level: 'info'
665
+ }
666
+ })
667
+ t.teardown(fastify.close.bind(fastify))
668
+ fastify.get('/error', function (req, reply) {
669
+ t.ok(req.log)
670
+ reply.status(415).send(new Error('something happened'))
671
+ })
672
+
673
+ await fastify.ready()
674
+ await fastify.listen({ port: 0, host: localhost })
675
+
676
+ await request(`http://${localhostForURL}:` + fastify.server.address().port + '/error')
677
+
678
+ for await (const [line] of on(stream, 'data')) {
679
+ t.match(line, lines.shift())
680
+ if (lines.length === 0) break
681
+ }
682
+ })
683
+
684
+ t.test('should redact the authorization header if so specified', async (t) => {
685
+ const lines = [
686
+ { msg: /Server listening at/ },
687
+ { req: { headers: { authorization: '[Redacted]' } }, msg: 'incoming request' },
688
+ { res: { statusCode: 200 }, msg: 'request completed' }
689
+ ]
690
+ t.plan(lines.length + 3)
691
+ const stream = split(JSON.parse)
692
+ const fastify = Fastify({
693
+ logger: {
694
+ stream,
695
+ redact: ['req.headers.authorization'],
696
+ level: 'info',
697
+ serializers: {
698
+ req (req) {
699
+ return {
700
+ method: req.method,
701
+ url: req.url,
702
+ headers: req.headers,
703
+ hostname: req.hostname,
704
+ remoteAddress: req.ip,
705
+ remotePort: req.socket.remotePort
706
+ }
707
+ }
708
+ }
709
+ }
710
+ })
711
+ t.teardown(fastify.close.bind(fastify))
712
+
713
+ fastify.get('/', function (req, reply) {
714
+ t.same(req.headers.authorization, 'Bearer abcde')
715
+ reply.send({ hello: 'world' })
716
+ })
717
+
718
+ await fastify.ready()
719
+ await fastify.listen({ port: 0, host: localhost })
720
+
721
+ await request({
722
+ method: 'GET',
723
+ path: '/',
724
+ host: localhost,
725
+ port: fastify.server.address().port,
726
+ headers: {
727
+ authorization: 'Bearer abcde'
728
+ }
729
+ }, function (response, body) {
730
+ t.equal(response.statusCode, 200)
731
+ t.same(body, JSON.stringify({ hello: 'world' }))
732
+ })
733
+
734
+ for await (const [line] of on(stream, 'data')) {
735
+ t.match(line, lines.shift())
736
+ if (lines.length === 0) break
737
+ }
738
+ })
739
+
740
+ t.test('should not log incoming request and outgoing response when disabled', async (t) => {
741
+ t.plan(3)
742
+ const stream = split(JSON.parse)
743
+ const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } })
744
+ t.teardown(fastify.close.bind(fastify))
745
+
746
+ fastify.get('/500', (req, reply) => {
747
+ reply.code(500).send(Error('500 error'))
748
+ })
749
+
750
+ await fastify.ready()
751
+
752
+ await fastify.inject({ method: 'GET', url: '/500' })
753
+
754
+ {
755
+ const [line] = await once(stream, 'data')
756
+ t.ok(line.reqId, 'reqId is defined')
757
+ t.equal(line.msg, '500 error', 'message is set')
758
+ }
759
+
760
+ // no more readable data
761
+ t.equal(stream.readableLength, 0)
762
+ })
763
+
764
+ t.test('should not log incoming request and outgoing response for 404 onBadUrl when disabled', async (t) => {
765
+ t.plan(3)
766
+ const stream = split(JSON.parse)
767
+ const fastify = Fastify({ disableRequestLogging: true, logger: { level: 'info', stream } })
768
+ t.teardown(fastify.close.bind(fastify))
769
+
770
+ await fastify.ready()
771
+
772
+ await fastify.inject({ method: 'GET', url: '/%c0' })
773
+
774
+ {
775
+ const [line] = await once(stream, 'data')
776
+ t.ok(line.reqId, 'reqId is defined')
777
+ t.equal(line.msg, 'Route GET:/%c0 not found', 'message is set')
778
+ }
779
+
780
+ // no more readable data
781
+ t.equal(stream.readableLength, 0)
782
+ })
783
+
784
+ t.test('should pass when using unWritable props in the logger option', (t) => {
785
+ t.plan(8)
786
+ const fastify = Fastify({
787
+ logger: Object.defineProperty({}, 'level', { value: 'info' })
788
+ })
789
+ t.teardown(fastify.close.bind(fastify))
790
+
791
+ t.equal(typeof fastify.log, 'object')
792
+ t.equal(typeof fastify.log.fatal, 'function')
793
+ t.equal(typeof fastify.log.error, 'function')
794
+ t.equal(typeof fastify.log.warn, 'function')
795
+ t.equal(typeof fastify.log.info, 'function')
796
+ t.equal(typeof fastify.log.debug, 'function')
797
+ t.equal(typeof fastify.log.trace, 'function')
798
+ t.equal(typeof fastify.log.child, 'function')
799
+ })
800
+
801
+ t.test('should be able to use a custom logger', (t) => {
802
+ t.plan(7)
803
+
804
+ const logger = {
805
+ fatal: (msg) => { t.equal(msg, 'fatal') },
806
+ error: (msg) => { t.equal(msg, 'error') },
807
+ warn: (msg) => { t.equal(msg, 'warn') },
808
+ info: (msg) => { t.equal(msg, 'info') },
809
+ debug: (msg) => { t.equal(msg, 'debug') },
810
+ trace: (msg) => { t.equal(msg, 'trace') },
811
+ child: () => logger
812
+ }
813
+
814
+ const fastify = Fastify({ logger })
815
+ t.teardown(fastify.close.bind(fastify))
816
+
817
+ fastify.log.fatal('fatal')
818
+ fastify.log.error('error')
819
+ fastify.log.warn('warn')
820
+ fastify.log.info('info')
821
+ fastify.log.debug('debug')
822
+ fastify.log.trace('trace')
823
+ const child = fastify.log.child()
824
+ t.equal(child, logger)
825
+ })
826
+
827
+ t.test('should create a default logger if provided one is invalid', (t) => {
828
+ t.plan(8)
829
+
830
+ const logger = new Date()
831
+
832
+ const fastify = Fastify({ logger })
833
+ t.teardown(fastify.close.bind(fastify))
834
+
835
+ t.equal(typeof fastify.log, 'object')
836
+ t.equal(typeof fastify.log.fatal, 'function')
837
+ t.equal(typeof fastify.log.error, 'function')
838
+ t.equal(typeof fastify.log.warn, 'function')
839
+ t.equal(typeof fastify.log.info, 'function')
840
+ t.equal(typeof fastify.log.debug, 'function')
841
+ t.equal(typeof fastify.log.trace, 'function')
842
+ t.equal(typeof fastify.log.child, 'function')
843
+ })
844
+
845
+ t.test('should not throw error when serializing custom req', (t) => {
846
+ t.plan(1)
847
+
848
+ const lines = []
849
+ const dest = new stream.Writable({
850
+ write: function (chunk, enc, cb) {
851
+ lines.push(JSON.parse(chunk))
852
+ cb()
853
+ }
854
+ })
855
+ const fastify = Fastify({ logger: { level: 'info', stream: dest } })
856
+ t.teardown(fastify.close.bind(fastify))
857
+
858
+ fastify.log.info({ req: {} })
859
+
860
+ t.same(lines[0].req, {})
861
+ })
862
+ })