fastify 4.14.1 → 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.
- package/README.md +8 -4
- package/docs/Guides/Database.md +7 -8
- package/docs/Guides/Ecosystem.md +16 -7
- package/docs/Guides/Getting-Started.md +1 -1
- package/docs/Guides/Migration-Guide-V4.md +21 -0
- package/docs/Guides/Plugins-Guide.md +1 -1
- package/docs/Guides/Prototype-Poisoning.md +31 -39
- package/docs/Guides/Recommendations.md +1 -1
- package/docs/Guides/Write-Type-Provider.md +3 -3
- package/docs/Reference/Errors.md +6 -0
- package/docs/Reference/Hooks.md +42 -9
- package/docs/Reference/Reply.md +2 -2
- package/docs/Reference/Routes.md +13 -2
- package/docs/Reference/Server.md +102 -35
- package/docs/Reference/Type-Providers.md +1 -1
- package/docs/Reference/TypeScript.md +3 -3
- package/docs/index.md +2 -2
- package/examples/benchmark/parser.js +47 -0
- package/fastify.js +26 -23
- package/lib/contentTypeParser.js +10 -2
- package/lib/error-serializer.js +9 -162
- package/lib/errors.js +5 -0
- package/lib/hooks.js +3 -0
- package/lib/logger.js +51 -41
- package/lib/route.js +0 -1
- package/lib/server.js +9 -4
- package/lib/validation.js +10 -0
- package/lib/warnings.js +2 -0
- package/package.json +17 -9
- package/test/close.test.js +91 -0
- package/test/content-parser.test.js +101 -0
- package/test/hooks.test.js +2 -3
- package/test/pretty-print.test.js +194 -62
- package/test/route-hooks.test.js +29 -0
- package/test/route.test.js +1 -1
- package/test/schema-feature.test.js +178 -0
- package/test/serial/logger.0.test.js +861 -0
- package/test/serial/logger.1.test.js +862 -0
- package/test/serial/tap-parallel-not-ok +0 -0
- package/test/server.test.js +10 -0
- package/test/types/hooks.test-d.ts +66 -11
- package/test/types/import.js +1 -1
- package/test/types/instance.test-d.ts +4 -0
- package/test/types/route.test-d.ts +106 -5
- package/test/types/type-provider.test-d.ts +77 -10
- package/types/errors.d.ts +1 -0
- package/types/hooks.d.ts +28 -0
- package/types/instance.d.ts +22 -2
- package/types/logger.d.ts +1 -1
- package/types/route.d.ts +41 -11
- package/test/logger.test.js +0 -1691
|
@@ -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
|
+
})
|