fastify 5.9.0 → 5.10.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/PROJECT_CHARTER.md +1 -1
- package/README.md +7 -10
- package/build/build-validation.js +2 -2
- package/docs/Guides/Delay-Accepting-Requests.md +1 -1
- package/docs/Guides/Ecosystem.md +10 -7
- package/docs/Guides/Getting-Started.md +2 -2
- package/docs/Guides/Migration-Guide-V4.md +2 -2
- package/docs/Guides/Migration-Guide-V5.md +1 -1
- package/docs/Guides/Plugins-Guide.md +1 -1
- package/docs/Guides/Prototype-Poisoning.md +3 -3
- package/docs/Guides/Serverless.md +6 -13
- package/docs/Guides/Style-Guide.md +1 -1
- package/docs/Reference/Errors.md +2 -0
- package/docs/Reference/Hooks.md +1 -1
- package/docs/Reference/Logging.md +3 -0
- package/docs/Reference/Request.md +2 -2
- package/docs/Reference/Server.md +107 -8
- package/docs/Reference/Type-Providers.md +24 -4
- package/docs/Reference/TypeScript.md +3 -3
- package/docs/Reference/Warnings.md +4 -0
- package/fastify.d.ts +5 -0
- package/fastify.js +22 -22
- package/lib/content-type-parser.js +1 -11
- package/lib/content-type.js +34 -1
- package/lib/context.js +0 -3
- package/lib/error-handler.js +7 -26
- package/lib/errors.js +7 -0
- package/lib/four-oh-four.js +8 -10
- package/lib/handle-request.js +2 -1
- package/lib/log-controller.js +169 -0
- package/lib/logger-factory.js +25 -4
- package/lib/reply.js +35 -44
- package/lib/req-id-gen-factory.js +4 -1
- package/lib/request.js +3 -3
- package/lib/route.js +27 -35
- package/lib/symbols.js +1 -1
- package/lib/validation.js +9 -0
- package/lib/warnings.js +19 -1
- package/package.json +3 -3
- package/test/content-type.test.js +20 -0
- package/test/genReqId.test.js +24 -0
- package/test/hooks.test.js +71 -0
- package/test/internals/errors.test.js +1 -1
- package/test/internals/logger.test.js +322 -0
- package/test/internals/reply.test.js +35 -4
- package/test/internals/request.test.js +10 -7
- package/test/logger/logging.test.js +40 -1
- package/test/stream.4.test.js +2 -2
- package/test/trust-proxy.test.js +49 -30
- package/types/errors.d.ts +1 -0
- package/types/instance.d.ts +2 -0
- package/types/logger.d.ts +22 -0
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
- [FSTWRN004](#FSTWRN004)
|
|
11
11
|
- [Fastify Deprecation Codes](#fastify-deprecation-codes)
|
|
12
12
|
- [FSTDEP022](#FSTDEP022)
|
|
13
|
+
- [FSTDEP023](#FSTDEP023)
|
|
14
|
+
- [FSTDEP024](#FSTDEP024)
|
|
13
15
|
|
|
14
16
|
## Warnings
|
|
15
17
|
|
|
@@ -57,3 +59,5 @@ Deprecation codes are supported by the Node.js CLI options:
|
|
|
57
59
|
| Code | Description | How to solve | Discussion |
|
|
58
60
|
| ---- | ----------- | ------------ | ---------- |
|
|
59
61
|
| <a id="FSTDEP022">FSTDEP022</a> | You are trying to access the deprecated router options on top option properties. | Use `options.routerOptions`. | [#5985](https://github.com/fastify/fastify/pull/5985)
|
|
62
|
+
| <a id="FSTDEP023">FSTDEP023</a> | `disableRequestLogging` top-level option is deprecated. | Pass a `LogController` instance via the `logController` option with `disableRequestLogging` in its constructor instead. |
|
|
63
|
+
| <a id="FSTDEP024">FSTDEP024</a> | `requestIdLogLabel` top-level option is deprecated. | Pass a `LogController` instance via the `logController` option with `requestIdLogLabel` in its constructor instead. |
|
package/fastify.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { FastifyInstance, FastifyListenOptions, PrintRoutesOptions } from './typ
|
|
|
17
17
|
import {
|
|
18
18
|
FastifyBaseLogger,
|
|
19
19
|
FastifyChildLoggerFactory,
|
|
20
|
+
LogController as LogControllerClass,
|
|
20
21
|
FastifyLogFn,
|
|
21
22
|
FastifyLoggerInstance,
|
|
22
23
|
FastifyLoggerOptions,
|
|
@@ -44,6 +45,7 @@ type Fastify = typeof fastify
|
|
|
44
45
|
|
|
45
46
|
declare namespace fastify {
|
|
46
47
|
export const errorCodes: FastifyErrorCodes
|
|
48
|
+
export { LogControllerClass as LogController }
|
|
47
49
|
|
|
48
50
|
export type FastifyHttp2SecureOptions<
|
|
49
51
|
Server extends http2.Http2SecureServer,
|
|
@@ -126,7 +128,9 @@ declare namespace fastify {
|
|
|
126
128
|
bodyLimit?: number,
|
|
127
129
|
handlerTimeout?: number,
|
|
128
130
|
maxParamLength?: number,
|
|
131
|
+
/** @deprecated Use the `logController` option with `disableRequestLogging` or `isLogDisabled` override instead. Will be removed in `fastify@6`. */
|
|
129
132
|
disableRequestLogging?: boolean | ((req: FastifyRequest) => boolean),
|
|
133
|
+
logController?: LogControllerClass,
|
|
130
134
|
exposeHeadRoutes?: boolean,
|
|
131
135
|
onProtoPoisoning?: ProtoAction,
|
|
132
136
|
onConstructorPoisoning?: ConstructorAction,
|
|
@@ -137,6 +141,7 @@ declare namespace fastify {
|
|
|
137
141
|
caseSensitive?: boolean,
|
|
138
142
|
allowUnsafeRegex?: boolean,
|
|
139
143
|
requestIdHeader?: string | false,
|
|
144
|
+
/** @deprecated Use the `logController` option with `requestIdLogLabel` instead. Will be removed in `fastify@6`. */
|
|
140
145
|
requestIdLogLabel?: string;
|
|
141
146
|
useSemicolonDelimiter?: boolean,
|
|
142
147
|
genReqId?: (req: RawRequestDefaultExpression<RawServer>) => string,
|
package/fastify.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const VERSION = '5.
|
|
3
|
+
const VERSION = '5.10.0'
|
|
4
4
|
|
|
5
5
|
const Avvio = require('avvio')
|
|
6
6
|
const http = require('node:http')
|
|
@@ -33,7 +33,8 @@ const {
|
|
|
33
33
|
kChildLoggerFactory,
|
|
34
34
|
kGenReqId,
|
|
35
35
|
kErrorHandlerAlreadySet,
|
|
36
|
-
kHandlerTimeout
|
|
36
|
+
kHandlerTimeout,
|
|
37
|
+
kLogController
|
|
37
38
|
} = require('./lib/symbols.js')
|
|
38
39
|
|
|
39
40
|
const { createServer } = require('./lib/server')
|
|
@@ -44,7 +45,7 @@ const decorator = require('./lib/decorate')
|
|
|
44
45
|
const ContentTypeParser = require('./lib/content-type-parser.js')
|
|
45
46
|
const SchemaController = require('./lib/schema-controller')
|
|
46
47
|
const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks')
|
|
47
|
-
const { createChildLogger, defaultChildLoggerFactory, createLogger } = require('./lib/logger-factory')
|
|
48
|
+
const { createChildLogger, defaultChildLoggerFactory, createLogger, createLogController, LogController } = require('./lib/logger-factory')
|
|
48
49
|
const pluginUtils = require('./lib/plugin-utils.js')
|
|
49
50
|
const { getGenReqId, reqIdGenFactory } = require('./lib/req-id-gen-factory.js')
|
|
50
51
|
const { buildRouting, validateBodyLimitOption, buildRouterOptions } = require('./lib/route')
|
|
@@ -79,7 +80,7 @@ const {
|
|
|
79
80
|
} = errorCodes
|
|
80
81
|
|
|
81
82
|
const { buildErrorHandler } = require('./lib/error-handler.js')
|
|
82
|
-
const { FSTWRN004 } = require('./lib/warnings.js')
|
|
83
|
+
const { FSTWRN004, FSTDEP023, FSTDEP024 } = require('./lib/warnings.js')
|
|
83
84
|
|
|
84
85
|
const initChannel = diagnostics.channel('fastify.initialization')
|
|
85
86
|
|
|
@@ -90,7 +91,7 @@ function fastify (serverOptions) {
|
|
|
90
91
|
const {
|
|
91
92
|
options,
|
|
92
93
|
genReqId,
|
|
93
|
-
|
|
94
|
+
logController,
|
|
94
95
|
hasLogger,
|
|
95
96
|
initialConfig
|
|
96
97
|
} = processOptions(serverOptions, defaultRoute, onBadUrl, onMaxParamLength)
|
|
@@ -216,6 +217,7 @@ function fastify (serverOptions) {
|
|
|
216
217
|
},
|
|
217
218
|
// expose logger instance
|
|
218
219
|
log: options.logger,
|
|
220
|
+
[kLogController]: logController,
|
|
219
221
|
// type provider
|
|
220
222
|
withTypeProvider,
|
|
221
223
|
// hooks
|
|
@@ -643,10 +645,7 @@ function fastify (serverOptions) {
|
|
|
643
645
|
const request = new Request(id, null, req, null, childLogger, routeEventContext)
|
|
644
646
|
const reply = new Reply(res, request, childLogger)
|
|
645
647
|
|
|
646
|
-
|
|
647
|
-
if (resolvedDisableRequestLogging === false) {
|
|
648
|
-
childLogger.info({ req: request }, 'incoming request')
|
|
649
|
-
}
|
|
648
|
+
routeEventContext.server[kLogController].incomingRequest(request, reply)
|
|
650
649
|
|
|
651
650
|
return options.frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
|
|
652
651
|
}
|
|
@@ -671,10 +670,7 @@ function fastify (serverOptions) {
|
|
|
671
670
|
const request = new Request(id, null, req, null, childLogger, routeEventContext)
|
|
672
671
|
const reply = new Reply(res, request, childLogger)
|
|
673
672
|
|
|
674
|
-
|
|
675
|
-
if (resolvedDisableRequestLogging === false) {
|
|
676
|
-
childLogger.info({ req: request }, 'incoming request')
|
|
677
|
-
}
|
|
673
|
+
routeEventContext.server[kLogController].incomingRequest(request, reply)
|
|
678
674
|
|
|
679
675
|
return options.frameworkErrors(new FST_ERR_MAX_PARAM_LENGTH(path), request, reply)
|
|
680
676
|
}
|
|
@@ -702,10 +698,7 @@ function fastify (serverOptions) {
|
|
|
702
698
|
const request = new Request(id, null, req, null, childLogger, routeEventContext)
|
|
703
699
|
const reply = new Reply(res, request, childLogger)
|
|
704
700
|
|
|
705
|
-
|
|
706
|
-
if (resolvedDisableRequestLogging === false) {
|
|
707
|
-
childLogger.info({ req: request }, 'incoming request')
|
|
708
|
-
}
|
|
701
|
+
routeEventContext.server[kLogController].incomingRequest(request, reply)
|
|
709
702
|
|
|
710
703
|
return options.frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)
|
|
711
704
|
}
|
|
@@ -876,9 +869,13 @@ function processOptions (options, defaultRoute, onBadUrl, onMaxParamLength) {
|
|
|
876
869
|
|
|
877
870
|
const requestIdHeader = typeof options.requestIdHeader === 'string' && options.requestIdHeader.length !== 0 ? options.requestIdHeader.toLowerCase() : (options.requestIdHeader === true && 'request-id')
|
|
878
871
|
const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId)
|
|
879
|
-
|
|
872
|
+
if (options.requestIdLogLabel !== undefined) {
|
|
873
|
+
FSTDEP024()
|
|
874
|
+
}
|
|
880
875
|
options.bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
|
|
881
|
-
|
|
876
|
+
if (options.disableRequestLogging !== undefined) {
|
|
877
|
+
FSTDEP023()
|
|
878
|
+
}
|
|
882
879
|
|
|
883
880
|
const ajvOptions = Object.assign({
|
|
884
881
|
customOptions: {},
|
|
@@ -894,6 +891,10 @@ function processOptions (options, defaultRoute, onBadUrl, onMaxParamLength) {
|
|
|
894
891
|
|
|
895
892
|
const { logger, hasLogger } = createLogger(options)
|
|
896
893
|
|
|
894
|
+
// the internal logger uses the input logger to execute the logging. This allows the user
|
|
895
|
+
// to customize every internal log line
|
|
896
|
+
const logController = createLogController(options)
|
|
897
|
+
|
|
897
898
|
// Update the options with the fixed values
|
|
898
899
|
options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout
|
|
899
900
|
options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout
|
|
@@ -901,8 +902,6 @@ function processOptions (options, defaultRoute, onBadUrl, onMaxParamLength) {
|
|
|
901
902
|
options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout
|
|
902
903
|
options.logger = logger
|
|
903
904
|
options.requestIdHeader = requestIdHeader
|
|
904
|
-
options.requestIdLogLabel = requestIdLogLabel
|
|
905
|
-
options.disableRequestLogging = disableRequestLogging
|
|
906
905
|
options.ajv = ajvOptions
|
|
907
906
|
options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler
|
|
908
907
|
options.allowErrorHandlerOverride = options.allowErrorHandlerOverride ?? defaultInitOptions.allowErrorHandlerOverride
|
|
@@ -930,7 +929,7 @@ function processOptions (options, defaultRoute, onBadUrl, onMaxParamLength) {
|
|
|
930
929
|
return {
|
|
931
930
|
options,
|
|
932
931
|
genReqId,
|
|
933
|
-
|
|
932
|
+
logController,
|
|
934
933
|
hasLogger,
|
|
935
934
|
initialConfig
|
|
936
935
|
}
|
|
@@ -1010,5 +1009,6 @@ function validateSchemaErrorFormatter (schemaErrorFormatter) {
|
|
|
1010
1009
|
*/
|
|
1011
1010
|
module.exports = fastify
|
|
1012
1011
|
module.exports.errorCodes = errorCodes
|
|
1012
|
+
module.exports.LogController = LogController
|
|
1013
1013
|
module.exports.fastify = fastify
|
|
1014
1014
|
module.exports.default = fastify
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { AsyncResource } = require('node:async_hooks')
|
|
4
|
-
const { FifoMap: Fifo
|
|
4
|
+
const { FifoMap: Fifo } = require('toad-cache')
|
|
5
5
|
const { parse: secureJsonParse } = require('secure-json-parse')
|
|
6
6
|
const ContentType = require('./content-type')
|
|
7
7
|
const {
|
|
@@ -39,15 +39,6 @@ function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning)
|
|
|
39
39
|
this.parserList = ['application/json', 'text/plain']
|
|
40
40
|
this.parserRegExpList = []
|
|
41
41
|
this.cache = new Fifo(100)
|
|
42
|
-
this.ctCache = new Lru(100)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
ContentTypeParser.prototype.getContentType = function (raw) {
|
|
46
|
-
let ct = this.ctCache.get(raw)
|
|
47
|
-
if (ct !== undefined) return ct
|
|
48
|
-
ct = new ContentType(raw)
|
|
49
|
-
this.ctCache.set(raw, ct)
|
|
50
|
-
return ct
|
|
51
42
|
}
|
|
52
43
|
|
|
53
44
|
ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
|
|
@@ -169,7 +160,6 @@ ContentTypeParser.prototype.removeAll = function () {
|
|
|
169
160
|
this.parserRegExpList = []
|
|
170
161
|
this.parserList = []
|
|
171
162
|
this.cache = new Fifo(100)
|
|
172
|
-
this.ctCache = new Lru(100)
|
|
173
163
|
}
|
|
174
164
|
|
|
175
165
|
ContentTypeParser.prototype.remove = function (contentType) {
|
package/lib/content-type.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { LruMap: Lru } = require('toad-cache')
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* keyValuePairsReg is used to split the parameters list into associated
|
|
5
7
|
* key value pairings. The leading `(?:^|;)\s*` anchor ensures the regex
|
|
@@ -35,6 +37,12 @@ const typeNameReg = /^[\w!#$%&'*+.^`|~-]+$/
|
|
|
35
37
|
*/
|
|
36
38
|
const subtypeNameReg = /^[\w!#$%&'*+.^`|~-]+\s*$/
|
|
37
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Content-Type internal shared cache
|
|
42
|
+
* @type {Lru<ContentType>}
|
|
43
|
+
*/
|
|
44
|
+
const cache = new Lru(100)
|
|
45
|
+
|
|
38
46
|
/**
|
|
39
47
|
* ContentType parses and represents the value of the content-type header.
|
|
40
48
|
*
|
|
@@ -49,6 +57,27 @@ class ContentType {
|
|
|
49
57
|
#parameters = new Map()
|
|
50
58
|
#string
|
|
51
59
|
|
|
60
|
+
/**
|
|
61
|
+
* The shared cache of ContentType instances. The cache is used to avoid
|
|
62
|
+
* creating multiple instances of ContentType for the same header value.
|
|
63
|
+
* @type {Lru<ContentType>}
|
|
64
|
+
*/
|
|
65
|
+
static get cache () { return cache }
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create a ContentType instance from a header value. If the value has been
|
|
69
|
+
* previously parsed, the cached instance will be returned.
|
|
70
|
+
* @param {string} headerValue
|
|
71
|
+
* @returns {ContentType | undefined}
|
|
72
|
+
*/
|
|
73
|
+
static from (headerValue) {
|
|
74
|
+
let contentType = cache.get(headerValue)
|
|
75
|
+
if (contentType !== undefined) return contentType
|
|
76
|
+
contentType = new ContentType(headerValue)
|
|
77
|
+
cache.set(headerValue, contentType)
|
|
78
|
+
return contentType
|
|
79
|
+
}
|
|
80
|
+
|
|
52
81
|
constructor (headerValue) {
|
|
53
82
|
if (headerValue == null || headerValue === '' || headerValue === 'undefined') {
|
|
54
83
|
return
|
|
@@ -106,7 +135,11 @@ class ContentType {
|
|
|
106
135
|
|
|
107
136
|
let matches = keyValuePairsReg.exec(paramsList)
|
|
108
137
|
while (matches) {
|
|
109
|
-
|
|
138
|
+
// https://httpwg.org/specs/rfc9110.html#parameter
|
|
139
|
+
// Parameter names are case-insensitive.
|
|
140
|
+
const key = matches[1].toLowerCase()
|
|
141
|
+
// Parameter values might or might not be case-sensitive,
|
|
142
|
+
// depending on the semantics of the parameter name.
|
|
110
143
|
const value = matches[2]
|
|
111
144
|
if (value[0] === '"') {
|
|
112
145
|
if (value.at(-1) !== '"') {
|
package/lib/context.js
CHANGED
|
@@ -6,7 +6,6 @@ const {
|
|
|
6
6
|
kSchemaErrorFormatter,
|
|
7
7
|
kErrorHandler,
|
|
8
8
|
kChildLoggerFactory,
|
|
9
|
-
kOptions,
|
|
10
9
|
kReply,
|
|
11
10
|
kRequest,
|
|
12
11
|
kBodyLimit,
|
|
@@ -24,7 +23,6 @@ function Context ({
|
|
|
24
23
|
schema,
|
|
25
24
|
handler,
|
|
26
25
|
config,
|
|
27
|
-
requestIdLogLabel,
|
|
28
26
|
childLoggerFactory,
|
|
29
27
|
errorHandler,
|
|
30
28
|
bodyLimit,
|
|
@@ -56,7 +54,6 @@ function Context ({
|
|
|
56
54
|
this.onRequestAbort = null
|
|
57
55
|
this.config = config
|
|
58
56
|
this.errorHandler = errorHandler || server[kErrorHandler]
|
|
59
|
-
this.requestIdLogLabel = requestIdLogLabel || server[kOptions].requestIdLogLabel
|
|
60
57
|
this.childLoggerFactory = childLoggerFactory || server[kChildLoggerFactory]
|
|
61
58
|
this._middie = null
|
|
62
59
|
this._parserOptions = {
|
package/lib/error-handler.js
CHANGED
|
@@ -8,7 +8,7 @@ const {
|
|
|
8
8
|
kReplyNextErrorHandler,
|
|
9
9
|
kReplyIsRunningOnErrorHook,
|
|
10
10
|
kRouteContext,
|
|
11
|
-
|
|
11
|
+
kLogController,
|
|
12
12
|
kDiagnosticsStore
|
|
13
13
|
} = require('./symbols.js')
|
|
14
14
|
|
|
@@ -36,12 +36,7 @@ function handleError (reply, error, cb) {
|
|
|
36
36
|
try {
|
|
37
37
|
reply.raw.writeHead(reply.raw.statusCode, reply[kReplyHeaders])
|
|
38
38
|
} catch (error) {
|
|
39
|
-
|
|
40
|
-
reply.log.warn(
|
|
41
|
-
{ req: reply.request, res: reply, err: error },
|
|
42
|
-
error?.message
|
|
43
|
-
)
|
|
44
|
-
}
|
|
39
|
+
reply.server[kLogController].writeHeadError(error, reply.request, reply)
|
|
45
40
|
reply.raw.writeHead(reply.raw.statusCode)
|
|
46
41
|
}
|
|
47
42
|
reply.raw.end(payload)
|
|
@@ -83,21 +78,7 @@ function handleError (reply, error, cb) {
|
|
|
83
78
|
function defaultErrorHandler (error, request, reply) {
|
|
84
79
|
setErrorHeaders(error, reply)
|
|
85
80
|
setErrorStatusCode(reply, error)
|
|
86
|
-
|
|
87
|
-
if (!reply.log[kDisableRequestLogging]) {
|
|
88
|
-
reply.log.info(
|
|
89
|
-
{ res: reply, err: error },
|
|
90
|
-
error?.message
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
} else {
|
|
94
|
-
if (!reply.log[kDisableRequestLogging]) {
|
|
95
|
-
reply.log.error(
|
|
96
|
-
{ req: request, res: reply, err: error },
|
|
97
|
-
error?.message
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
81
|
+
request.server[kLogController].defaultErrorLog(error, request, reply)
|
|
101
82
|
reply.send(error)
|
|
102
83
|
}
|
|
103
84
|
|
|
@@ -123,10 +104,10 @@ function fallbackErrorHandler (error, reply, cb) {
|
|
|
123
104
|
}))
|
|
124
105
|
}
|
|
125
106
|
} catch (err) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
107
|
+
// error is always FST_ERR_SCH_SERIALIZATION_BUILD
|
|
108
|
+
// because this is called from route/compileSchemasForSerialization
|
|
109
|
+
reply.server[kLogController].serializerError(err, reply.request, reply, { statusCode: res.statusCode })
|
|
110
|
+
|
|
130
111
|
reply.code(500)
|
|
131
112
|
payload = serializeError(new FST_ERR_FAILED_ERROR_SERIALIZATION(err.message, error.message))
|
|
132
113
|
}
|
package/lib/errors.js
CHANGED
|
@@ -243,6 +243,13 @@ const codes = {
|
|
|
243
243
|
TypeError
|
|
244
244
|
),
|
|
245
245
|
|
|
246
|
+
FST_ERR_LOG_INVALID_LOG_CONTROLLER: createError(
|
|
247
|
+
'FST_ERR_LOG_INVALID_LOG_CONTROLLER',
|
|
248
|
+
"logController option must be an instance of LogController, got '%s'.",
|
|
249
|
+
500,
|
|
250
|
+
TypeError
|
|
251
|
+
),
|
|
252
|
+
|
|
246
253
|
/**
|
|
247
254
|
* reply
|
|
248
255
|
*/
|
package/lib/four-oh-four.js
CHANGED
|
@@ -11,7 +11,8 @@ const {
|
|
|
11
11
|
kFourOhFourLevelInstance,
|
|
12
12
|
kFourOhFourContext,
|
|
13
13
|
kHooks,
|
|
14
|
-
kErrorHandler
|
|
14
|
+
kErrorHandler,
|
|
15
|
+
kLogController
|
|
15
16
|
} = require('./symbols.js')
|
|
16
17
|
const { lifecycleHooks } = require('./hooks')
|
|
17
18
|
const { buildErrorHandler } = require('./error-handler.js')
|
|
@@ -29,7 +30,7 @@ const { getGenReqId } = require('./req-id-gen-factory.js')
|
|
|
29
30
|
* kFourOhFourContext: the context in the reply object where the handler will be executed
|
|
30
31
|
*/
|
|
31
32
|
function fourOhFour (options) {
|
|
32
|
-
const { logger
|
|
33
|
+
const { logger } = options
|
|
33
34
|
|
|
34
35
|
// 404 router, used for handling encapsulated 404 handlers
|
|
35
36
|
const router = FindMyWay({
|
|
@@ -52,14 +53,10 @@ function fourOhFour (options) {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
function basic404 (request, reply) {
|
|
56
|
+
request.server[kLogController].routeNotFound(request, reply)
|
|
55
57
|
const { url, method } = request.raw
|
|
56
|
-
const message = `Route ${method}:${url} not found`
|
|
57
|
-
const resolvedDisableRequestLogging = typeof disableRequestLogging === 'function' ? disableRequestLogging(request.raw) : disableRequestLogging
|
|
58
|
-
if (!resolvedDisableRequestLogging) {
|
|
59
|
-
request.log.info(message)
|
|
60
|
-
}
|
|
61
58
|
reply.code(404).send({
|
|
62
|
-
message
|
|
59
|
+
message: `Route ${method}:${url} not found`,
|
|
63
60
|
error: 'Not Found',
|
|
64
61
|
statusCode: 404
|
|
65
62
|
})
|
|
@@ -181,11 +178,12 @@ function fourOhFour (options) {
|
|
|
181
178
|
const id = getGenReqId(fourOhFourContext.server, req)
|
|
182
179
|
const childLogger = createChildLogger(fourOhFourContext, logger, req, id)
|
|
183
180
|
|
|
184
|
-
childLogger.info({ req }, 'incoming request')
|
|
185
|
-
|
|
186
181
|
const request = new Request(id, null, req, null, childLogger, fourOhFourContext)
|
|
187
182
|
const reply = new Reply(res, request, childLogger)
|
|
188
183
|
|
|
184
|
+
fourOhFourContext.server[kLogController].incomingRequest(request, reply)
|
|
185
|
+
|
|
186
|
+
// Since this could be a bug, we log it without checking if the logging is disabled
|
|
189
187
|
request.log.warn('the default handler for 404 did not catch this, this is likely a fastify bug, please report it')
|
|
190
188
|
request.log.warn(router.prettyPrint())
|
|
191
189
|
reply.code(404).send(new FST_ERR_NOT_FOUND())
|
package/lib/handle-request.js
CHANGED
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
kRequestContentType,
|
|
15
15
|
kDiagnosticsStore
|
|
16
16
|
} = require('./symbols')
|
|
17
|
+
const ContentType = require('./content-type')
|
|
17
18
|
|
|
18
19
|
const channels = diagnostics.tracingChannel('fastify.request.handler')
|
|
19
20
|
|
|
@@ -55,7 +56,7 @@ function handleRequest (err, request, reply) {
|
|
|
55
56
|
// Conditional assignment to avoid creating a new ContentType instance
|
|
56
57
|
// It can be assigned when accessing the .mediaType in hooks
|
|
57
58
|
if (!request[kRequestContentType]) {
|
|
58
|
-
request[kRequestContentType] =
|
|
59
|
+
request[kRequestContentType] = ContentType.from(ctHeader)
|
|
59
60
|
}
|
|
60
61
|
if (request[kRequestContentType].isValid === false) {
|
|
61
62
|
reply[kReplyIsError] = true
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { defaultInitOptions } = require('./initial-config-validation')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Internal logger layer that wraps Fastify's log calls with
|
|
7
|
+
* request-lifecycle awareness (disable-check, level selection).
|
|
8
|
+
* Keeps the surface intentionally small so hot paths stay fast.
|
|
9
|
+
*
|
|
10
|
+
* Users can extend this class to customize internal log lines.
|
|
11
|
+
*/
|
|
12
|
+
class LogController {
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} [options]
|
|
15
|
+
* @param {boolean | ((req: object) => boolean)} [options.disableRequestLogging=false]
|
|
16
|
+
* When `true` (or a function returning `true`), per-request log lines
|
|
17
|
+
* (incoming, completed, errors) are suppressed.
|
|
18
|
+
* @param {string} [options.requestIdLogLabel='reqId']
|
|
19
|
+
* The label used for the request identifier when logging the request.
|
|
20
|
+
*/
|
|
21
|
+
constructor (options) {
|
|
22
|
+
const opts = options || {}
|
|
23
|
+
this.disableRequestLogging = opts.disableRequestLogging || defaultInitOptions.disableRequestLogging
|
|
24
|
+
this.isDisableRequestLoggingFunction = typeof this.disableRequestLogging === 'function'
|
|
25
|
+
this.requestIdLogLabel = opts.requestIdLogLabel || defaultInitOptions.requestIdLogLabel
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Checks whether request logging is disabled for the given request.
|
|
30
|
+
*
|
|
31
|
+
* @param {object} req Raw or Fastify request object.
|
|
32
|
+
* @returns {boolean} `true` when logging should be skipped.
|
|
33
|
+
*/
|
|
34
|
+
isLogDisabled (req) {
|
|
35
|
+
return this.isDisableRequestLoggingFunction
|
|
36
|
+
? this.disableRequestLogging(req)
|
|
37
|
+
: this.disableRequestLogging
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Logs an incoming request at `info` level.
|
|
42
|
+
*
|
|
43
|
+
* @param {object} request Fastify request object.
|
|
44
|
+
* @param {object} reply Fastify reply object.
|
|
45
|
+
* @param {object} [metadata] Extra contextual data (unused).
|
|
46
|
+
*/
|
|
47
|
+
incomingRequest (request, reply, metadata) {
|
|
48
|
+
if (this.isLogDisabled(request)) { return }
|
|
49
|
+
|
|
50
|
+
request.log.info({ req: request }, 'incoming request')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Logs the outcome of a completed request.
|
|
55
|
+
* Uses `error` level when an error is present, `info` otherwise.
|
|
56
|
+
*
|
|
57
|
+
* @param {Error | null} error Error that occurred during the response, if any.
|
|
58
|
+
* @param {object} request Fastify request object.
|
|
59
|
+
* @param {object} reply Fastify reply object.
|
|
60
|
+
* @param {object} [metadata] Extra contextual data (unused).
|
|
61
|
+
*/
|
|
62
|
+
requestCompleted (error, request, reply, metadata) {
|
|
63
|
+
if (this.isLogDisabled(request)) { return }
|
|
64
|
+
|
|
65
|
+
if (error) {
|
|
66
|
+
reply.log.error({ res: reply, err: error, responseTime: reply.elapsedTime }, 'request errored')
|
|
67
|
+
} else {
|
|
68
|
+
reply.log.info({ res: reply, responseTime: reply.elapsedTime }, 'request completed')
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Logs an error handled by the default error handler.
|
|
74
|
+
* Uses `error` for 5xx status codes, `info` for 4xx.
|
|
75
|
+
*
|
|
76
|
+
* @param {Error} error The error being handled.
|
|
77
|
+
* @param {object} request Fastify request object.
|
|
78
|
+
* @param {object} reply Fastify reply object (must have `statusCode`).
|
|
79
|
+
* @param {object} [metadata] Extra contextual data (unused).
|
|
80
|
+
*/
|
|
81
|
+
defaultErrorLog (error, request, reply, metadata) {
|
|
82
|
+
if (this.isLogDisabled(request)) { return }
|
|
83
|
+
|
|
84
|
+
if (reply.statusCode >= 500) {
|
|
85
|
+
reply.log.error({ req: request, res: reply, err: error }, error?.message)
|
|
86
|
+
} else {
|
|
87
|
+
reply.log.info({ res: reply, err: error }, error?.message)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Logs stream-level errors that occur after headers have been sent.
|
|
93
|
+
* Premature closes are logged at `info`; other errors at `warn`.
|
|
94
|
+
*
|
|
95
|
+
* @param {Error} error The stream error.
|
|
96
|
+
* @param {object} request Fastify request object.
|
|
97
|
+
* @param {object} reply Fastify reply object.
|
|
98
|
+
* @param {object} [metadata] Extra contextual data (unused).
|
|
99
|
+
*/
|
|
100
|
+
streamError (error, request, reply, metadata) {
|
|
101
|
+
if (this.isLogDisabled(request)) { return }
|
|
102
|
+
|
|
103
|
+
if (error.code === 'ERR_STREAM_PREMATURE_CLOSE') {
|
|
104
|
+
reply.log.info({ res: reply }, 'stream closed prematurely')
|
|
105
|
+
} else {
|
|
106
|
+
reply.log.warn({ err: error }, 'response terminated with an error with headers already sent')
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Logs a "route not found" message at `info` level.
|
|
112
|
+
* Used by the default 404 handler.
|
|
113
|
+
*
|
|
114
|
+
* @param {object} request Fastify request object.
|
|
115
|
+
* @param {object} reply Fastify reply object.
|
|
116
|
+
* @param {object} [metadata] Extra contextual data (unused).
|
|
117
|
+
*/
|
|
118
|
+
routeNotFound (request, reply, metadata) {
|
|
119
|
+
if (this.isLogDisabled(request)) { return }
|
|
120
|
+
|
|
121
|
+
const { url, method } = request.raw
|
|
122
|
+
request.log.info(`Route ${method}:${url} not found`)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Logs a warning when writeHead fails during error handling.
|
|
127
|
+
*
|
|
128
|
+
* @param {Error} error The error thrown by writeHead.
|
|
129
|
+
* @param {object} request Fastify request object.
|
|
130
|
+
* @param {object} reply Fastify reply object.
|
|
131
|
+
* @param {object} [metadata] Extra contextual data (unused).
|
|
132
|
+
*/
|
|
133
|
+
writeHeadError (error, request, reply, metadata) {
|
|
134
|
+
if (this.isLogDisabled(request)) { return }
|
|
135
|
+
|
|
136
|
+
reply.log.warn(
|
|
137
|
+
{ req: request, res: reply, err: error },
|
|
138
|
+
error?.message
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Logs an error when the serializer for a given status code fails.
|
|
144
|
+
*
|
|
145
|
+
* @param {Error} error The serialization error.
|
|
146
|
+
* @param {object} request Fastify request object.
|
|
147
|
+
* @param {object} reply Fastify reply object.
|
|
148
|
+
* @param {object} metadata Extra contextual data.
|
|
149
|
+
* @param {number} metadata.statusCode The status code that triggered the serializer.
|
|
150
|
+
*/
|
|
151
|
+
serializerError (error, request, reply, metadata) {
|
|
152
|
+
if (this.isLogDisabled(request)) { return }
|
|
153
|
+
|
|
154
|
+
reply.log.error({ err: error, statusCode: metadata.statusCode }, 'The serializer for the given status code failed')
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Logs a 503 Service Unavailable when the server is closing.
|
|
159
|
+
* Always emitted (not gated by `disableRequestLogging`).
|
|
160
|
+
*
|
|
161
|
+
* @param {import('../fastify').FastifyBaseLogger} logger
|
|
162
|
+
* @param {import('../fastify').FastifyInstance} server
|
|
163
|
+
*/
|
|
164
|
+
serviceUnavailable (logger, server) {
|
|
165
|
+
logger.info({ res: { statusCode: 503 } }, 'request aborted - refusing to accept new requests as server is closing')
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = { LogController }
|
package/lib/logger-factory.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { performance } = require('node:perf_hooks')
|
|
4
|
+
|
|
3
5
|
const {
|
|
4
6
|
FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED,
|
|
5
7
|
FST_ERR_LOG_INVALID_LOGGER_CONFIG,
|
|
6
8
|
FST_ERR_LOG_INVALID_LOGGER_INSTANCE,
|
|
7
|
-
FST_ERR_LOG_INVALID_LOGGER
|
|
9
|
+
FST_ERR_LOG_INVALID_LOGGER,
|
|
10
|
+
FST_ERR_LOG_INVALID_LOG_CONTROLLER
|
|
8
11
|
} = require('./errors')
|
|
12
|
+
const {
|
|
13
|
+
kLogController
|
|
14
|
+
} = require('./symbols.js')
|
|
15
|
+
const { LogController } = require('./log-controller.js')
|
|
9
16
|
|
|
10
17
|
/**
|
|
11
18
|
* Utility for creating a child logger with the appropriate bindings, logger factory
|
|
@@ -21,7 +28,7 @@ const {
|
|
|
21
28
|
*/
|
|
22
29
|
function createChildLogger (context, logger, req, reqId, loggerOpts) {
|
|
23
30
|
const loggerBindings = {
|
|
24
|
-
[context.requestIdLogLabel]: reqId
|
|
31
|
+
[context.server[kLogController].requestIdLogLabel]: reqId
|
|
25
32
|
}
|
|
26
33
|
const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req)
|
|
27
34
|
|
|
@@ -122,15 +129,29 @@ function createLogger (options) {
|
|
|
122
129
|
return { logger, hasLogger: true }
|
|
123
130
|
}
|
|
124
131
|
|
|
132
|
+
function createLogController (options) {
|
|
133
|
+
const userController = options.logController
|
|
134
|
+
if (!userController) {
|
|
135
|
+
return new LogController(options)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (userController instanceof LogController) {
|
|
139
|
+
return userController
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
throw new FST_ERR_LOG_INVALID_LOG_CONTROLLER(typeof userController)
|
|
143
|
+
}
|
|
144
|
+
|
|
125
145
|
function now () {
|
|
126
|
-
|
|
127
|
-
return (ts[0] * 1e3) + (ts[1] / 1e6)
|
|
146
|
+
return performance.now()
|
|
128
147
|
}
|
|
129
148
|
|
|
130
149
|
module.exports = {
|
|
131
150
|
createChildLogger,
|
|
132
151
|
defaultChildLoggerFactory,
|
|
152
|
+
createLogController,
|
|
133
153
|
createLogger,
|
|
154
|
+
LogController,
|
|
134
155
|
validateLogger,
|
|
135
156
|
now
|
|
136
157
|
}
|