Skip to content

Commit ec17333

Browse files
authored
feat: log requests refused before processing starts (fastify#4600)
Fastify can reject requests outright before any of the normal request processing begins right now: an unsupported HTTP version, or if the server is currently closing and `return503OnClosing` is true. Before this change, these requests wouldn't appear in the logs at all, which can make debugging them kind of confusing. This change adds a log message for these requests, so that they can be found by operators and a strange looking status code can be explained more easily.
1 parent d74b2b4 commit ec17333

File tree

3 files changed

+56
-17
lines changed

3 files changed

+56
-17
lines changed

docs/Reference/Server.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,9 @@ fastify.addHook('onResponse', (req, reply, done) => {
382382
```
383383

384384
Please note that this setting will also disable an error log written by the
385-
default `onResponse` hook on reply callback errors.
385+
default `onResponse` hook on reply callback errors. Other log messages
386+
emitted by Fastify will stay enabled, like deprecation warnings and messages
387+
emitted when requests are received while the server is closing.
386388

387389
### `serverFactory`
388390
<a id="custom-http-server"></a>

lib/route.js

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,25 @@ function buildRouting (options) {
394394

395395
// HTTP request entry point, the routing has already been executed
396396
function routeHandler (req, res, params, context, query) {
397+
const id = genReqId(req)
398+
399+
const loggerBinding = {
400+
[requestIdLogLabel]: id
401+
}
402+
403+
const loggerOpts = {
404+
level: context.logLevel
405+
}
406+
407+
if (context.logSerializers) {
408+
loggerOpts.serializers = context.logSerializers
409+
}
410+
const childLogger = logger.child(loggerBinding, loggerOpts)
411+
childLogger[kDisableRequestLogging] = disableRequestLogging
412+
397413
// TODO: The check here should be removed once https://github.com/nodejs/node/issues/43115 resolve in core.
398414
if (!validateHTTPVersion(req.httpVersion)) {
415+
childLogger.info({ res: { statusCode: 505 } }, 'request aborted - invalid HTTP version')
399416
const message = '{"error":"HTTP Version Not Supported","message":"HTTP Version Not Supported","statusCode":505}'
400417
const headers = {
401418
'Content-Type': 'application/json',
@@ -424,6 +441,7 @@ function buildRouting (options) {
424441
}
425442
res.writeHead(503, headers)
426443
res.end('{"error":"Service Unavailable","message":"Service Unavailable","statusCode":503}')
444+
childLogger.info({ res: { statusCode: 503 } }, 'request aborted - refusing to accept new requests as server is closing')
427445
return
428446
}
429447
}
@@ -445,22 +463,6 @@ function buildRouting (options) {
445463
req.headers[kRequestAcceptVersion] = undefined
446464
}
447465

448-
const id = genReqId(req)
449-
450-
const loggerBinding = {
451-
[requestIdLogLabel]: id
452-
}
453-
454-
const loggerOpts = {
455-
level: context.logLevel
456-
}
457-
458-
if (context.logSerializers) {
459-
loggerOpts.serializers = context.logSerializers
460-
}
461-
const childLogger = logger.child(loggerBinding, loggerOpts)
462-
childLogger[kDisableRequestLogging] = disableRequestLogging
463-
464466
const request = new context.Request(id, params, req, query, childLogger, context)
465467
const reply = new context.Reply(res, request, childLogger)
466468

test/close.test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const test = t.test
77
const Fastify = require('..')
88
const { Client } = require('undici')
99
const semver = require('semver')
10+
const split = require('split2')
1011

1112
test('close callback', t => {
1213
t.plan(4)
@@ -303,6 +304,40 @@ t.test('Current opened connection should not accept new incoming connections', t
303304
})
304305
})
305306

307+
t.test('rejected incoming connections should be logged', t => {
308+
t.plan(2)
309+
const stream = split(JSON.parse)
310+
const fastify = Fastify({
311+
forceCloseConnections: false,
312+
logger: {
313+
stream,
314+
level: 'info'
315+
}
316+
})
317+
318+
const messages = []
319+
stream.on('data', message => {
320+
messages.push(message)
321+
})
322+
fastify.get('/', (req, reply) => {
323+
fastify.close()
324+
setTimeout(() => {
325+
reply.send({ hello: 'world' })
326+
}, 250)
327+
})
328+
329+
fastify.listen({ port: 0 }, err => {
330+
t.error(err)
331+
const instance = new Client('http://localhost:' + fastify.server.address().port)
332+
// initial request to trigger close
333+
instance.request({ path: '/', method: 'GET' })
334+
// subsequent request should be rejected
335+
instance.request({ path: '/', method: 'GET' }).then(() => {
336+
t.ok(messages.find(message => message.msg.includes('request aborted')))
337+
})
338+
})
339+
})
340+
306341
test('Cannot be reopened the closed server without listen callback', async t => {
307342
t.plan(2)
308343
const fastify = Fastify()

0 commit comments

Comments
 (0)