@nodefony/http 10.0.0-beta.3 → 10.0.0-beta.5
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/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorate.js +1 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.153.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/decorateParam.js +1 -2
- package/dist/index.js +13 -10
- package/dist/nodefony/command/assetsPublishCommand.js +5 -3
- package/dist/nodefony/command/certificatesCommand.js +5 -3
- package/dist/nodefony/command/networkCommand.js +5 -3
- package/dist/nodefony/command/proxyGenerateCommand.js +8 -3
- package/dist/nodefony/config/config.js +3 -3
- package/dist/nodefony/config/defineModuleConfig.js +1 -2
- package/dist/nodefony/interfaces/IRequestTrace.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +2 -2
- package/dist/nodefony/service/ProfilerAdminApi.js +13 -3
- package/dist/nodefony/service/audit-logger.js +23 -30
- package/dist/nodefony/service/certificates.js +97 -13
- package/dist/nodefony/service/error-renderer.js +29 -12
- package/dist/nodefony/service/http-kernel.js +198 -50
- package/dist/nodefony/service/pretty-request-logger.js +15 -13
- package/dist/nodefony/service/request-logger.js +24 -16
- package/dist/nodefony/service/reverse-proxy.js +3 -3
- package/dist/nodefony/service/servers/clientError.js +22 -3
- package/dist/nodefony/service/servers/server-h2c.js +126 -0
- package/dist/nodefony/service/servers/server-http.js +5 -8
- package/dist/nodefony/service/servers/server-https.js +12 -24
- package/dist/nodefony/service/servers/server-static.js +3 -3
- package/dist/nodefony/service/servers/server-websocket-secure.js +10 -18
- package/dist/nodefony/service/servers/server-websocket.js +10 -18
- package/dist/nodefony/service/servers/serverShutdown.js +78 -18
- package/dist/nodefony/service/servers/wsHeartbeat.js +1 -2
- package/dist/nodefony/service/sessions/sessions-service.js +7 -14
- package/dist/nodefony/service/trace.js +1 -2
- package/dist/nodefony/service/upload/upload-service.js +5 -8
- package/dist/nodefony/service/x509.js +1 -2
- package/dist/nodefony/src/assets/collectAssets.js +1 -2
- package/dist/nodefony/src/assets/prebuiltUi.js +1 -2
- package/dist/nodefony/src/context/Context.js +78 -43
- package/dist/nodefony/src/context/domainMatcher.js +1 -2
- package/dist/nodefony/src/context/forwarded.js +1 -2
- package/dist/nodefony/src/context/http/HttpContext.js +28 -6
- package/dist/nodefony/src/context/http/Request.js +33 -29
- package/dist/nodefony/src/context/http/Response.js +23 -6
- package/dist/nodefony/src/context/http/SseStream.js +1 -2
- package/dist/nodefony/src/context/http/parser.js +36 -10
- package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
- package/dist/nodefony/src/context/http2/Request.js +4 -8
- package/dist/nodefony/src/context/http2/Response.js +41 -9
- package/dist/nodefony/src/context/metaData.js +1 -2
- package/dist/nodefony/src/context/requestId.js +1 -2
- package/dist/nodefony/src/context/requestTrace.js +104 -0
- package/dist/nodefony/src/context/responseEnded.js +1 -2
- package/dist/nodefony/src/context/timingMode.js +21 -0
- package/dist/nodefony/src/context/transport.js +45 -0
- package/dist/nodefony/src/context/trustProxy.js +1 -2
- package/dist/nodefony/src/context/unsentResponse.js +30 -0
- package/dist/nodefony/src/context/websocket/Response.js +33 -6
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +104 -25
- package/dist/nodefony/src/context/websocket/isWebsocketContext.js +17 -0
- package/dist/nodefony/src/context/websocket/websocketUrl.js +1 -2
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +1 -2
- package/dist/nodefony/src/context/websocket/wsCloseReason.js +1 -2
- package/dist/nodefony/src/context/websocket/wsLogContent.js +1 -2
- package/dist/nodefony/src/cookies/cookie.js +3 -7
- package/dist/nodefony/src/errors/httpError.js +1 -2
- package/dist/nodefony/src/errors/thrownMessage.js +1 -2
- package/dist/nodefony/src/profiler/FrameProfile.js +23 -2
- package/dist/nodefony/src/profiler/Profiler.js +104 -32
- package/dist/nodefony/src/profiler/aggregate.js +442 -0
- package/dist/nodefony/src/proxy/forward.js +29 -5
- package/dist/nodefony/src/proxy/generateProxyConfig.js +14 -6
- package/dist/nodefony/src/proxy/rules.js +12 -6
- package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +1 -2
- package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +1 -2
- package/dist/nodefony/src/rateLimit/rateLimitFilters.js +1 -2
- package/dist/nodefony/src/servers/http2Limits.js +50 -0
- package/dist/nodefony/src/servers/kernelServers.js +12 -3
- package/dist/nodefony/src/servers/portBinder.js +41 -10
- package/dist/nodefony/src/servers/upgradeDispatch.js +86 -7
- package/dist/nodefony/src/session/session.js +2 -3
- package/dist/nodefony/src/session/sessionStoreFailure.js +1 -2
- package/dist/nodefony/src/session/storage/MemorySessionStorage.js +1 -2
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +1 -2
- package/dist/nodefony/src/session/storage/sessionFilters.js +1 -2
- package/dist/nodefony/src/session/storage/sessionSort.js +1 -2
- package/dist/types/index.d.ts +9 -3
- package/dist/types/nodefony/config/config.d.ts +4 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +72 -4
- package/dist/types/nodefony/interfaces/IRequest.d.ts +9 -4
- package/dist/types/nodefony/interfaces/IRequestTrace.d.ts +56 -0
- package/dist/types/nodefony/interfaces/IReverseProxy.d.ts +8 -0
- package/dist/types/nodefony/interfaces/IRouting.d.ts +26 -0
- package/dist/types/nodefony/interfaces/index.d.ts +3 -2
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +3 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +3 -11
- package/dist/types/nodefony/service/certificates.d.ts +55 -4
- package/dist/types/nodefony/service/error-renderer.d.ts +31 -0
- package/dist/types/nodefony/service/http-kernel.d.ts +96 -10
- package/dist/types/nodefony/service/servers/clientError.d.ts +16 -0
- package/dist/types/nodefony/service/servers/server-h2c.d.ts +51 -0
- package/dist/types/nodefony/service/servers/server-http.d.ts +3 -3
- package/dist/types/nodefony/service/servers/server-https.d.ts +4 -4
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +1 -3
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +1 -3
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +30 -16
- package/dist/types/nodefony/src/context/Context.d.ts +38 -5
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +16 -2
- package/dist/types/nodefony/src/context/http/Request.d.ts +20 -15
- package/dist/types/nodefony/src/context/http/Response.d.ts +11 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +18 -2
- package/dist/types/nodefony/src/context/http2/Request.d.ts +1 -1
- package/dist/types/nodefony/src/context/http2/Response.d.ts +27 -5
- package/dist/types/nodefony/src/context/requestTrace.d.ts +64 -0
- package/dist/types/nodefony/src/context/timingMode.d.ts +23 -0
- package/dist/types/nodefony/src/context/transport.d.ts +53 -0
- package/dist/types/nodefony/src/context/unsentResponse.d.ts +16 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +18 -2
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +58 -8
- package/dist/types/nodefony/src/context/websocket/isWebsocketContext.d.ts +15 -0
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +28 -12
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +79 -111
- package/dist/types/nodefony/src/profiler/aggregate.d.ts +132 -0
- package/dist/types/nodefony/src/proxy/forward.d.ts +14 -2
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +16 -0
- package/dist/types/nodefony/src/proxy/rules.d.ts +3 -0
- package/dist/types/nodefony/src/servers/http2Limits.d.ts +37 -0
- package/dist/types/nodefony/src/servers/kernelServers.d.ts +10 -1
- package/dist/types/nodefony/src/servers/portBinder.d.ts +29 -5
- package/dist/types/nodefony/src/servers/upgradeDispatch.d.ts +33 -3
- package/docs/cookies.md +85 -78
- package/docs/diagnostics-channel.md +303 -0
- package/docs/index.md +6 -1
- package/docs/observabilite.md +159 -81
- package/docs/rate-limit.md +83 -83
- package/docs/reverse-proxy.md +72 -70
- package/docs/servers.md +338 -224
- package/docs/session.md +179 -179
- package/docs/sse.md +24 -20
- package/docs/upload.md +120 -117
- package/package.json +8 -9
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.js +0 -6
package/docs/index.md
CHANGED
|
@@ -43,7 +43,8 @@ Trois parcours selon ce que tu viens faire. L'ordre compte : chaque étape suppo
|
|
|
43
43
|
3. [Pipeline de requête](../../../../../docs/architecture/pipeline-requete.md) — où se branchent
|
|
44
44
|
rate-limit, en-têtes et firewall.
|
|
45
45
|
4. [Rate-limit](rate-limit.md) — plafonner le débit par client avant que la charge n'atteigne le contrôleur.
|
|
46
|
-
5. [Observabilité](observabilite.md) — corréler les logs par `requestId` pour diagnostiquer à chaud
|
|
46
|
+
5. [Observabilité](observabilite.md) — corréler les logs par `requestId` pour diagnostiquer à chaud ;
|
|
47
|
+
[Canal de diagnostic](diagnostics-channel.md) pour brancher un outil de traçage.
|
|
47
48
|
6. [Sécurité](../../security/docs/index.md) — le pare-feu applicatif se pose par-dessus ce module.
|
|
48
49
|
|
|
49
50
|
**Je fais du temps réel** — WebSocket dans le même contexte que le web.
|
|
@@ -66,6 +67,7 @@ Le tableau pour choisir vite ; les cards en dessous pour savoir ce qu'on y trouv
|
|
|
66
67
|
| [Upload & corps](upload.md) | parser le corps et recevoir des fichiers | formulaires, imports multipart, API JSON |
|
|
67
68
|
| [Rate-limit](rate-limit.md) | plafonner le débit par client (429) | protéger une API d'un flood ou d'un abus |
|
|
68
69
|
| [Observabilité](observabilite.md) | tracer et journaliser chaque requête | débugger en prod, corréler des logs |
|
|
70
|
+
| [Canal de diagnostic](diagnostics-channel.md) | publier le cycle de chaque requête à un outil de traçage, sans monkey-patch | brancher OpenTelemetry, Datadog ou Sentry |
|
|
69
71
|
| [Proxy inverse](reverse-proxy.md) | relayer un préfixe d'URL vers un autre serveur, sur la même origine | servir Vite en dev, adosser un service interne sans CORS |
|
|
70
72
|
| [Pipeline de requête](../../../../../docs/architecture/pipeline-requete.md) | l'ordre exact des étapes, HTTP comme WS | tu débugges « pourquoi ça passe / ça bloque ici » |
|
|
71
73
|
|
|
@@ -89,6 +91,9 @@ Le tableau pour choisir vite ; les cards en dessous pour savoir ce qu'on y trouv
|
|
|
89
91
|
{ "icon": "📈", "title": "observabilite", "href": "observabilite.md",
|
|
90
92
|
"desc": "Observer les requêtes : lignes de log (pretty ou JSON), requestId de corrélation, W3C Trace Context, trace des frames WebSocket, redaction et sampling d'audit. Où partent les logs est traité par la page Syslog du cœur.",
|
|
91
93
|
"meta": "débugger en prod, corréler les logs par requête" },
|
|
94
|
+
{ "icon": "📡", "title": "diagnostics-channel", "href": "diagnostics-channel.md",
|
|
95
|
+
"desc": "Le TracingChannel nodefony.http.request : chaque requête HTTP et chaque handshake WebSocket publiés avec route, gabarit, module, requestId, statut réel et durée. Zéro coût sans abonné ; correspondance avec les attributs OpenTelemetry.",
|
|
96
|
+
"meta": "brancher un APM sans patcher le framework" },
|
|
92
97
|
{ "icon": "↪️", "title": "reverse-proxy", "href": "reverse-proxy.md",
|
|
93
98
|
"desc": "Relayer un préfixe d'URL vers un autre serveur sans quitter l'origine de la page : HTTP et WebSocket, en-têtes de connexion jamais transmis, X-Forwarded-* et Via posés, barrière d'hôte appliquée avant le relais. C'est par lui que Vite est servi en développement.",
|
|
94
99
|
"meta": "une origine, un certificat — pas de CORS ni de contenu mixte" },
|
package/docs/observabilite.md
CHANGED
|
@@ -111,19 +111,19 @@ Le différenciateur — **HTTP et WebSocket dans le même pipeline** — se retr
|
|
|
111
111
|
un `requestId`, un `traceparent` et un contrat de logger **uniques** couvrent les deux transports.
|
|
112
112
|
|
|
113
113
|
**Le `requestId` est un citoyen du contexte, pas un décor.** Il naît dans le constructeur de base
|
|
114
|
-
`Context.requestId = randomUUID()` (`Context.ts
|
|
115
|
-
(`http-kernel.ts
|
|
114
|
+
`Context.requestId = randomUUID()` (`Context.ts#Context.csrfProtect`), voyage dans l'ALS via `RequestContext.run(...)`
|
|
115
|
+
(`http-kernel.ts#HttpKernel.configureRateLimit` pour HTTP, `http-kernel.ts#HttpKernel.finishWebsocket` pour WS), et se lit de n'importe où avec
|
|
116
116
|
`RequestContext.getRequestId()` — un controller, un service, un adapter ORM, sans jamais le threader.
|
|
117
117
|
|
|
118
118
|
**La ligne de bilan est branchable.** Le kernel ne code pas un format en dur : il consulte un
|
|
119
|
-
`IRequestLogger` (`IRequestLogger.ts
|
|
120
|
-
`http-kernel.ts
|
|
119
|
+
`IRequestLogger` (`IRequestLogger.ts#IRequestLogger`) résolu au boot depuis la config (`applyRequestLoggerFromConfig`,
|
|
120
|
+
`http-kernel.ts#HttpKernel.applyRequestLoggerFromConfig`), remplaçable à chaud par `httpKernel.setRequestLogger(...)` (`http-kernel.ts#HttpKernel.setRequestLogger`).
|
|
121
121
|
Trois formateurs sont livrés ; un quatrième maison s'écrit en implémentant l'interface.
|
|
122
122
|
|
|
123
123
|
**Zero Trust sur l'entrée cliente.** Un `X-Request-Id` fourni par le client finit réfléchi en réponse,
|
|
124
124
|
écrit dans les logs et propagé en ALS — donc une valeur non assainie ouvrirait log-injection (CR/LF) et
|
|
125
125
|
throw natif de `setHeader`. Nodefony **valide puis adopte, ou rejette** (`sanitizeRequestId`,
|
|
126
|
-
`requestId.ts
|
|
126
|
+
`requestId.ts#sanitizeRequestId`) : jamais nettoyer (masquerait l'abus), toujours retomber sur l'UUID serveur.
|
|
127
127
|
|
|
128
128
|
## 🚀 Démarrage rapide
|
|
129
129
|
|
|
@@ -222,84 +222,141 @@ GET 200 /trace/whoami 3.1ms 127.0.0.1 [demo-abc]
|
|
|
222
222
|
|
|
223
223
|
### Le `requestId` — génération, adoption, réflexion
|
|
224
224
|
|
|
225
|
-
| Étape | Où
|
|
226
|
-
| ------------------- |
|
|
227
|
-
| Génération | `Context.requestId = randomUUID()` (`Context.ts
|
|
228
|
-
| Adoption HTTP | `sanitizeRequestId(headers["x-request-id"])` (`HttpContext.ts
|
|
229
|
-
| Adoption WS | `sanitizeRequestId(...)` au handshake (`WebsocketContext.ts
|
|
230
|
-
| Réflexion HTTP/1.1 | `Response.setHeader("x-request-id", …)` (`Response.ts
|
|
231
|
-
| Réflexion HTTP/2 | `this.headers["x-request-id"] = requestId` (`http2/Response.ts
|
|
232
|
-
| ALS (HTTP) | `RequestContext.run({ requestId, … })` (`http-kernel.ts
|
|
233
|
-
| ALS (WS) | `RequestContext.run({ requestId, … })` (`http-kernel.ts
|
|
234
|
-
| Capture dans le log | `Pdu.requestId = Pdu.requestIdProvider?.()` (`Pdu.ts
|
|
225
|
+
| Étape | Où | Comportement |
|
|
226
|
+
| ------------------- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
227
|
+
| Génération | `Context.requestId = randomUUID()` (`Context.ts#Context.csrfProtect`) | UUID v4 posé dans le constructeur de base — HTTP **et** WS. |
|
|
228
|
+
| Adoption HTTP | `sanitizeRequestId(headers["x-request-id"])` (`HttpContext.ts#HttpContext.response`) | Remplace l'UUID **si** la valeur cliente est sûre, sinon on garde l'UUID. |
|
|
229
|
+
| Adoption WS | `sanitizeRequestId(...)` au handshake (`WebsocketContext.ts#WebsocketContext.constructor`) | Même validation, stable sur toute la durée de la socket (handshake → close). |
|
|
230
|
+
| Réflexion HTTP/1.1 | `Response.setHeader("x-request-id", …)` (`Response.ts#HttpResponse.setHeader`) | Écrit dans `writeHead()`, sur **chaque** réponse. |
|
|
231
|
+
| Réflexion HTTP/2 | `this.headers["x-request-id"] = requestId` (`http2/Response.ts#Http2Response.writeHead`) | Sinon les réponses du port 5152 sortiraient sans corrélation. |
|
|
232
|
+
| ALS (HTTP) | `RequestContext.run({ requestId, … })` (`http-kernel.ts#HttpKernel.configureRateLimit`) | Ouvre la bulle → tout `Pdu` créé dedans est tagué. |
|
|
233
|
+
| ALS (WS) | `RequestContext.run({ requestId, … })` (`http-kernel.ts#HttpKernel.configureRateLimit`) | Handshake **et** messages : la bulle ouverte à la connexion est reliée à chaque message par `AsyncResource.bind`, sinon l'identité résolue au handshake se perdrait au premier message. |
|
|
234
|
+
| Capture dans le log | `Pdu.requestId = Pdu.requestIdProvider?.()` (`Pdu.ts#Pdu.sysLogSeverity`) | Provider injectable branché sur l'ALS côté Node — 0 lecture côté navigateur. |
|
|
235
235
|
|
|
236
236
|
> [!IMPORTANT]
|
|
237
237
|
> Les logs de **fin** de requête (bilan `req`, `onClose`) sont émis **hors** de la bulle ALS (déjà
|
|
238
|
-
> refermée). L'override `Context.log()` (`Context.ts
|
|
238
|
+
> refermée). L'override `Context.log()` (`Context.ts#Context.log`) rouvre alors une micro-bulle depuis
|
|
239
239
|
> `this.requestId` pour que le `Pdu` capture quand même la corrélation — sinon la ligne d'entrée d'une
|
|
240
240
|
> trace serait la seule à ne PAS porter son `requestId`.
|
|
241
241
|
|
|
242
242
|
### La trace W3C — `traceparent`
|
|
243
243
|
|
|
244
|
-
Nodefony implémente **W3C Trace Context** (le code s'y réfère explicitement, `trace.ts
|
|
244
|
+
Nodefony implémente **W3C Trace Context** (le code s'y réfère explicitement, `trace.ts`). À l'entrée :
|
|
245
245
|
|
|
246
246
|
- Un `traceparent` entrant **valide** est prolongé : on garde `version`/`traceId`/`flags` et on frappe
|
|
247
|
-
un nouveau `spanId` (on est un maillon enfant) — `resolveTraceparent()` (`trace.ts
|
|
247
|
+
un nouveau `spanId` (on est un maillon enfant) — `resolveTraceparent()` (`trace.ts#resolveTraceparent`).
|
|
248
248
|
- Absent ou malformé → on **forge** une trace neuve (`00-<traceId>-<spanId>-01`, échantillonnée).
|
|
249
249
|
- La validation refuse `version=ff` et un `traceId`/`spanId` tout-à-zéro — `parseTraceparent()`
|
|
250
|
-
(`trace.ts
|
|
250
|
+
(`trace.ts#parseTraceparent`), conforme à la spec (le récepteur NE DOIT PAS propager ces valeurs).
|
|
251
251
|
|
|
252
|
-
Le `traceparent` résolu est propagé en ALS **et** réfléchi sur la réponse HTTP (`context/http/Response.ts
|
|
252
|
+
Le `traceparent` résolu est propagé en ALS **et** réfléchi sur la réponse HTTP (`context/http/Response.ts#HttpResponse.writeHead`). Côté
|
|
253
253
|
**WebSocket**, il est propagé en ALS mais **pas** réfléchi dans la réponse de handshake — la bibliothèque
|
|
254
|
-
`ws` n'expose pas proprement ce chemin (`http-kernel.ts
|
|
254
|
+
`ws` n'expose pas proprement ce chemin (`http-kernel.ts#HttpKernel.finishTeardownHttp`) ; la corrélation reste visible côté serveur.
|
|
255
255
|
|
|
256
256
|
### Le contrat de logger — `IRequestLogger`
|
|
257
257
|
|
|
258
|
-
Le kernel tient un `IRequestLogger` singleton (`http-kernel.ts
|
|
259
|
-
de bilan, au teardown, via `Context.logRequest()` (`Context.ts
|
|
260
|
-
`WebsocketContext.logRequest()` (`WebsocketContext.ts
|
|
261
|
-
(`IRequestLogger.ts
|
|
258
|
+
Le kernel tient un `IRequestLogger` singleton (`http-kernel.ts#HttpKernel.secContentTypeOptions`) et lui délègue le rendu de la ligne
|
|
259
|
+
de bilan, au teardown, via `Context.logRequest()` (`Context.ts#Context.logRequest`) côté HTTP et
|
|
260
|
+
`WebsocketContext.logRequest()` (`WebsocketContext.ts#WebsocketContext.logRequest`) côté WS. Le contrat a trois méthodes
|
|
261
|
+
(`IRequestLogger.ts#IRequestLogger`) :
|
|
262
262
|
|
|
263
263
|
- `renderHttp(context, error?)` → `{ text, severity, msgid }` remis à `context.log()`.
|
|
264
264
|
- `renderWebsocket(context, error?, acceptedProtocol?)` → idem pour le WS.
|
|
265
265
|
- `shouldSample?(context, error?)` — **portillon** évalué AVANT le rendu : `false` = ligne sautée sans
|
|
266
266
|
aucune allocation ni `JSON.stringify` (levier perf du logger d'audit).
|
|
267
267
|
|
|
268
|
+
### La durée d'une requête — une horloge, mesurée partout
|
|
269
|
+
|
|
270
|
+
`durationMs` est **une seule valeur**, que lisent la ligne d'accès JSON, la ligne lisible du
|
|
271
|
+
développement, le profil du Studio et la trace verbose. Elle vient d'une horloge propre à chaque
|
|
272
|
+
requête (`Context.ts#Context.stopClock`) :
|
|
273
|
+
|
|
274
|
+
- **elle démarre** à la construction du contexte, dès que la requête atteint le noyau ;
|
|
275
|
+
- **elle s'arrête** au démontage, sur l'événement `close` de la réponse : le dernier octet vient
|
|
276
|
+
d'être remis au système, ou le client est parti (`http-kernel.ts#HttpKernel.teardownHttp`).
|
|
277
|
+
|
|
278
|
+
Aucun lecteur ne relit l'horloge pour son compte : le journal et le Studio affichent la même durée
|
|
279
|
+
pour la même requête.
|
|
280
|
+
|
|
281
|
+
| Compté dans la durée | Hors de la durée |
|
|
282
|
+
| ------------------------------------------------------ | -------------------------------------------------------------------------- |
|
|
283
|
+
| routage, pare-feu, session, action, rendu | l'acceptation TCP et la négociation TLS |
|
|
284
|
+
| l'écriture de la réponse, jusqu'au dernier octet remis | la lecture des en-têtes par Node, avant que la requête n'atteigne le noyau |
|
|
285
|
+
| l'attente d'un client lent qui lit la réponse | ce qui suit le `close` : hooks `onAfterResponse` et `onFinish` |
|
|
286
|
+
|
|
287
|
+
> [!NOTE]
|
|
288
|
+
> **Pourquoi jusqu'au dernier octet, et pas jusqu'à la fin de l'action ?** Parce que c'est la
|
|
289
|
+
> définition des outils qui liront cette ligne. OpenTelemetry ferme le span serveur sur `close`, et
|
|
290
|
+
> la métrique `http.server.request.duration` doit valoir la durée de ce span. nginx mesure
|
|
291
|
+
> `$request_time` jusqu'à l'écriture du journal, une fois les derniers octets envoyés. Un p99 de
|
|
292
|
+
> l'application se compare ainsi directement à celui du proxy placé devant elle. Le travail du
|
|
293
|
+
> serveur seul, hors réseau, se lit dans les **phases**.
|
|
294
|
+
|
|
295
|
+
Trois cas à connaître :
|
|
296
|
+
|
|
297
|
+
- **Un flux** (SSE, téléchargement long) dure ce que dure le flux, comme chez nginx.
|
|
298
|
+
- **Un client parti avant la fin** (statut `499`) : la durée court jusqu'à son départ.
|
|
299
|
+
- **WebSocket** : la ligne d'accès d'un handshake porte la durée du handshake, pas celle de la
|
|
300
|
+
connexion. Chaque invocation du pont RPC a sa propre horloge, dans son profil
|
|
301
|
+
(`FrameProfile.ts#FrameProfile.stopClock`).
|
|
302
|
+
|
|
303
|
+
#### Deux niveaux de mesure : la durée, puis les phases
|
|
304
|
+
|
|
305
|
+
| `timing.enabled` | Durée (`durationMs`) | Phases (waterfall) | Pour quoi |
|
|
306
|
+
| ---------------- | ---------------------------- | ------------------ | ------------------------------------------------------- |
|
|
307
|
+
| `null` (défaut) | partout, production comprise | hors production | le cas courant |
|
|
308
|
+
| `true` | partout | partout | un diagnostic à chaud en production |
|
|
309
|
+
| `false` | nulle part | nulle part | un banc qui veut le coût du pipeline sans aucune mesure |
|
|
310
|
+
|
|
311
|
+
La règle a une seule implémentation (`timingMode.ts#resolveTimingMode`). La durée coûte deux lectures
|
|
312
|
+
d'horloge par requête ; les phases, une lecture par borne, un tableau et un index par requête. C'est
|
|
313
|
+
pourquoi seules les phases restent coupées en production.
|
|
314
|
+
|
|
315
|
+
La trace verbose (`timing.verbose`) nomme les deux grandeurs côte à côte :
|
|
316
|
+
`TRACE phases [total 15.25ms · Σ 12.80ms] parse=0.50ms · action=12.30ms`. Le total est la durée de la
|
|
317
|
+
requête, Σ la somme des phases : l'écart montre le temps passé hors de toute phase, envoi au client
|
|
318
|
+
compris.
|
|
319
|
+
|
|
268
320
|
### La trace des frames WebSocket
|
|
269
321
|
|
|
270
322
|
Chaque frame WS (RECEIVE / SEND / BROADCAST) peut être tracée pour le Suivi de requête (Studio). Le
|
|
271
|
-
formatage du contenu est **pur** et **borné** — `formatWsLogContent()` (`wsLogContent.ts
|
|
272
|
-
`WebsocketContext.logMessageContent()` (`WebsocketContext.ts
|
|
323
|
+
formatage du contenu est **pur** et **borné** — `formatWsLogContent()` (`wsLogContent.ts#formatWsLogContent`), appelé par
|
|
324
|
+
`WebsocketContext.logMessageContent()` (`WebsocketContext.ts#WebsocketContext.logMessageContent`) :
|
|
273
325
|
|
|
274
|
-
- `string` → tronquée à `WS_LOG_CONTENT_CAP` (4096, `wsLogContent.ts
|
|
326
|
+
- `string` → tronquée à `WS_LOG_CONTENT_CAP` (4096, `wsLogContent.ts#WS_LOG_CONTENT_CAP`) + ellipse.
|
|
275
327
|
- **binaire** (Buffer, ArrayBuffer, TypedArray, Blob, `Buffer[]`) → résumé `[binary N B]`, **jamais**
|
|
276
|
-
sérialisé (`binaryByteLength()`, `wsLogContent.ts
|
|
328
|
+
sérialisé (`binaryByteLength()`, `wsLogContent.ts#binaryByteLength`) — un dump `{"0":..,"1":..}` serait énorme et faux.
|
|
277
329
|
- objet « JSON » → `JSON.stringify` compact tronqué, repli `String(...)` sur cycle/`bigint`.
|
|
278
330
|
|
|
279
331
|
Le tout est **gaté hors production** : `logMessageContent` court-circuite avant toute construction de
|
|
280
|
-
chaîne quand l'env est `production` (`WebsocketContext.ts
|
|
332
|
+
chaîne quand l'env est `production` (`WebsocketContext.ts#WebsocketContext.broadcast`) → 0 surcoût sur le hot path WS.
|
|
281
333
|
|
|
282
334
|
## ⚙️ Configuration
|
|
283
335
|
|
|
284
|
-
|
|
336
|
+
Deux réglages d'app, côté cœur : le format des lignes (bloc `log`) et ce que le chronométrage mesure
|
|
337
|
+
(bloc `timing`).
|
|
285
338
|
|
|
286
|
-
| Option | Type | Défaut | Effet
|
|
287
|
-
| ------------------- | ----------------------------------------- | ------ |
|
|
288
|
-
| `log.requestFormat` | `auto` \| `default` \| `pretty` \| `json` | `auto` | Format de la ligne de bilan. `auto` = pretty en dev / json en prod (`schema.ts
|
|
339
|
+
| Option | Type | Défaut | Effet |
|
|
340
|
+
| ------------------- | ----------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
341
|
+
| `log.requestFormat` | `auto` \| `default` \| `pretty` \| `json` | `auto` | Format de la ligne de bilan. `auto` = pretty en dev / json en prod (`schema.ts#serverSchema`). |
|
|
342
|
+
| `timing.enabled` | `boolean` \| `null` | `null` | `null` : durée partout, phases hors production. `true` : phases aussi en production. `false` : ni durée ni phases. Surchargeable sans redéployer : `NF__APP__TIMING__ENABLED`. |
|
|
289
343
|
|
|
290
|
-
|
|
344
|
+
Une clé inconnue sous `timing` (`timing: { enable: false }`) arrête le démarrage en la nommant : retirée
|
|
345
|
+
en silence, elle laisserait l'horloge allumée chez qui croit l'avoir coupée.
|
|
291
346
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
|
295
|
-
|
|
|
296
|
-
| `
|
|
347
|
+
`auto` est résolu au boot selon l'environnement (`applyRequestLoggerFromConfig`, `http-kernel.ts#HttpKernel.applyRequestLoggerFromConfig`) :
|
|
348
|
+
|
|
349
|
+
| Valeur | Formateur | Rendu |
|
|
350
|
+
| --------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
351
|
+
| `pretty` | `PrettyRequestLogger` | 1 ligne colorée `GET 200 /x 12.3ms 127.0.0.1 [a1b2c3d4]` (`pretty-request-logger.ts#PrettyRequestLogger.renderHttp`). |
|
|
352
|
+
| `json` | `JsonAuditLogger` | 1 objet JSON canonique par requête (`audit-logger.ts#JsonAuditLogger`). |
|
|
353
|
+
| `default` | `DefaultRequestLogger` | Format legacy verbeux `URL : … FROM : … ID : <uuid>` (`request-logger.ts#DefaultRequestLogger.renderHttp`). |
|
|
297
354
|
|
|
298
355
|
> [!NOTE]
|
|
299
356
|
> Le **réglage fin** du logger JSON (`sampleRate`, `includeStack`, `maxCauseDepth`, `nominal`) n'est pas
|
|
300
357
|
> un champ déclaré du schéma d'app : il se pose **programmatiquement**, en construisant le logger et en
|
|
301
358
|
> l'injectant — `httpKernel.setRequestLogger(new JsonAuditLogger({ sampleRate: 10 }))` (options :
|
|
302
|
-
> `JsonAuditLoggerOptions`, `audit-logger.ts
|
|
359
|
+
> `JsonAuditLoggerOptions`, `audit-logger.ts#JsonAuditLoggerOptions`). L'override programmatique gagne toujours sur la config.
|
|
303
360
|
|
|
304
361
|
## 🧰 Les trois formateurs (et le contrat)
|
|
305
362
|
|
|
@@ -314,31 +371,42 @@ Choisir en cinq secondes :
|
|
|
314
371
|
### `PrettyRequestLogger` — une ligne pour l'humain
|
|
315
372
|
|
|
316
373
|
Le plus grand gain en dev : `méthode statut url durée remote [id]`, avec couleur du statut (vert 2xx,
|
|
317
|
-
jaune 4xx, rouge 5xx — `colorizeStatus()`, `pretty-request-logger.ts
|
|
318
|
-
premiers caractères (`shortId()`, `pretty-request-logger.ts
|
|
319
|
-
|
|
374
|
+
jaune 4xx, rouge 5xx — `colorizeStatus()`, `pretty-request-logger.ts#colorizeStatus`) et `requestId` tronqué aux 8
|
|
375
|
+
premiers caractères (`shortId()`, `pretty-request-logger.ts#shortId`). La durée est celle de l'horloge de
|
|
376
|
+
la requête, la même que dans la ligne JSON (`pretty-request-logger.ts#formatDuration`) ; `---` quand
|
|
377
|
+
`timing.enabled` vaut `false`.
|
|
320
378
|
|
|
321
379
|
### `JsonAuditLogger` — un PDU par requête, pour la machine
|
|
322
380
|
|
|
323
|
-
Émet un `AuditLogEntry` canonique (`audit-logger.ts
|
|
381
|
+
Émet un `AuditLogEntry` canonique (`audit-logger.ts#AuditLogEntry.ts`) : `ts`, `requestId`, `userIdentifier` (appelant authentifié, posé par le pare-feu sur `context.userIdentifier`), `type`, `method`,
|
|
324
382
|
`url`, `status`, `durationMs`, `remoteAddress`, `host`, `userAgent`, phases, erreur enrichie (nom, code,
|
|
325
383
|
`errorType`, `cause` bornée à 5, stack **dev seulement**). Deux propriétés majeures :
|
|
326
384
|
|
|
327
385
|
- **Redaction par construction** : `Authorization` et `Cookie` ne sont **jamais** sérialisés — seuls des
|
|
328
|
-
drapeaux `hasAuthorization`/`hasCookie` le sont (`audit-logger.ts
|
|
329
|
-
- **Sampling déterministe** : `shouldSample()` (`audit-logger.ts
|
|
386
|
+
drapeaux `hasAuthorization`/`hasCookie` le sont (`audit-logger.ts#AuditLogEntry.hasCookie`).
|
|
387
|
+
- **Sampling déterministe** : `shouldSample()` (`audit-logger.ts#JsonAuditLogger.shouldSample`) garde 1 requête nominale sur N mais
|
|
330
388
|
**jamais** une erreur ni un `status >= 400` — on ne perd aucun échec ; compteur, pas de RNG.
|
|
389
|
+
- **Durée présente en production** : `durationMs` est la durée de la requête, de son arrivée au
|
|
390
|
+
dernier octet de la réponse, en millisecondes. C'est la même valeur que dans le Studio (voir
|
|
391
|
+
[La durée d'une requête](#la-durée-dune-requête--une-horloge-mesurée-partout)). Le détail par phase
|
|
392
|
+
(`phases`) reste réservé au développement, ou à `timing.enabled: true`.
|
|
393
|
+
|
|
394
|
+
Destinée à une machine, cette ligne se lit dans un outil d'observabilité : une application générée
|
|
395
|
+
livre deux **tableaux de bord d'exemple** (Grafana sur Loki, OpenSearch Dashboards) sous
|
|
396
|
+
`docker/examples/dashboards/`, à importer. Côté OpenSearch, le transport range la ligne sous
|
|
397
|
+
`http.*` (statut, famille, chemin, durée) par un pipeline d'ingestion — la page Syslog du cœur le
|
|
398
|
+
détaille.
|
|
331
399
|
|
|
332
400
|
### `DefaultRequestLogger` — le format legacy
|
|
333
401
|
|
|
334
|
-
Singleton sans état, 0 allocation par requête (`request-logger.ts
|
|
402
|
+
Singleton sans état, 0 allocation par requête (`request-logger.ts#DefaultRequestLogger.renderHttp`). Conserve le format historique
|
|
335
403
|
`URL : … FROM : … ORIGIN : … ID : <uuid>`, avec `Accept-Protocol` en plus côté WebSocket.
|
|
336
404
|
|
|
337
405
|
### Écrire son propre formateur
|
|
338
406
|
|
|
339
|
-
Implémenter `IRequestLogger` (`IRequestLogger.ts
|
|
340
|
-
5424 texte, OpenTelemetry logs… `httpKernel.setRequestLogger(monLogger)` (`http-kernel.ts
|
|
341
|
-
formateurs et le type sont exportés depuis `@nodefony/http` (`index.ts
|
|
407
|
+
Implémenter `IRequestLogger` (`IRequestLogger.ts#IRequestLogger`) et l'injecter — NCSA Common Log Format, syslog RFC
|
|
408
|
+
5424 texte, OpenTelemetry logs… `httpKernel.setRequestLogger(monLogger)` (`http-kernel.ts#HttpKernel.setRequestLogger`). Les trois
|
|
409
|
+
formateurs et le type sont exportés depuis `@nodefony/http` (`index.ts`).
|
|
342
410
|
|
|
343
411
|
## 🔐 Sécurité
|
|
344
412
|
|
|
@@ -348,15 +416,15 @@ sensibles à la fois — d'où une validation stricte.
|
|
|
348
416
|
<!-- prettier-ignore -->
|
|
349
417
|
| Menace | Vecteur | Défense |
|
|
350
418
|
| --- | --- | --- |
|
|
351
|
-
| Log injection (CR/LF) | `X-Request-Id: a\r\nFAKE LINE` écrit tel quel dans les logs | Allowlist `[A-Za-z0-9._-]{1,128}` (`requestId.ts
|
|
352
|
-
| Response splitting / DoS | Caractère de contrôle / non-ASCII → throw `setHeader` natif (500) | Même allowlist ; valeur non conforme **rejetée**, pas nettoyée — `sanitizeRequestId()` (`requestId.ts
|
|
353
|
-
| Log flooding | `X-Request-Id` géant | Borne `MAX_REQUEST_ID_LENGTH = 128` (`requestId.ts
|
|
354
|
-
| Fuite de secret dans l'audit | `Authorization` / `Cookie` sérialisés dans le log JSON | Drapeaux de présence seuls — valeurs jamais écrites (`audit-logger.ts
|
|
355
|
-
| Fuite de stack en prod | `error.stack` dans les logs d'audit publics | `includeStack` par défaut `false` en production (`audit-logger.ts
|
|
419
|
+
| Log injection (CR/LF) | `X-Request-Id: a\r\nFAKE LINE` écrit tel quel dans les logs | Allowlist `[A-Za-z0-9._-]{1,128}` (`requestId.ts#SAFE_REQUEST_ID`) — CR/LF exclus. |
|
|
420
|
+
| Response splitting / DoS | Caractère de contrôle / non-ASCII → throw `setHeader` natif (500) | Même allowlist ; valeur non conforme **rejetée**, pas nettoyée — `sanitizeRequestId()` (`requestId.ts#sanitizeRequestId`). |
|
|
421
|
+
| Log flooding | `X-Request-Id` géant | Borne `MAX_REQUEST_ID_LENGTH = 128` (`requestId.ts#MAX_REQUEST_ID_LENGTH`). |
|
|
422
|
+
| Fuite de secret dans l'audit | `Authorization` / `Cookie` sérialisés dans le log JSON | Drapeaux de présence seuls — valeurs jamais écrites (`audit-logger.ts#JsonAuditLogger.renderHttp`). |
|
|
423
|
+
| Fuite de stack en prod | `error.stack` dans les logs d'audit publics | `includeStack` par défaut `false` en production (`audit-logger.ts#JsonAuditLogger`). |
|
|
356
424
|
|
|
357
425
|
> [!WARNING]
|
|
358
426
|
> On **rejette** plutôt que d'assainir un `requestId` invalide : nettoyer donnerait au client un faux
|
|
359
|
-
> contrôle sur l'identifiant et masquerait la tentative d'abus (`requestId.ts
|
|
427
|
+
> contrôle sur l'identifiant et masquerait la tentative d'abus (`requestId.ts#sanitizeRequestId`).
|
|
360
428
|
|
|
361
429
|
## ⚡ Performance & mémoire
|
|
362
430
|
|
|
@@ -364,15 +432,20 @@ La ligne de bilan et la corrélation sont sur le **chemin chaud** : leur coût e
|
|
|
364
432
|
Les choix visibles dans le code :
|
|
365
433
|
|
|
366
434
|
- **`requestId` gratuit à l'entropie** — `randomUUID()` de Node met en cache l'entropie (128 UUID/appel
|
|
367
|
-
système) ; les ids W3C amortissent de même via un pool de 4096 o (`randomHex()`, `trace.ts
|
|
435
|
+
système) ; les ids W3C amortissent de même via un pool de 4096 o (`randomHex()`, `trace.ts#randomHex`).
|
|
368
436
|
- **Provider ALS lu paresseusement** — un `Pdu` hors bulle ne paie que 1 test de référence ; le provider
|
|
369
|
-
reste `null` côté navigateur/debugbar (`Pdu.ts
|
|
437
|
+
reste `null` côté navigateur/debugbar (`Pdu.ts#guid`) → 0 lecture, 0 allocation.
|
|
370
438
|
- **Sampling avant rendu** — `shouldSample()` saute la requête **avant** `renderHttp` : 0 objet, 0
|
|
371
|
-
`toISOString`, 0 `JSON.stringify`, 0 `Pdu` au ring (`audit-logger.ts
|
|
439
|
+
`toISOString`, 0 `JSON.stringify`, 0 `Pdu` au ring (`audit-logger.ts#JsonAuditLogger.shouldSample`).
|
|
372
440
|
- **Audit nominal coupable** — l'option `nominal` coupe le log des 2xx/3xx si le sink texte est `null`
|
|
373
|
-
(l'entrée n'atteindrait aucune destination) — ~5,9 % du profil CPU récupérés (`audit-logger.ts
|
|
441
|
+
(l'entrée n'atteindrait aucune destination) — ~5,9 % du profil CPU récupérés (`audit-logger.ts#JsonAuditLoggerOptions.sampleRate`).
|
|
442
|
+
- **Durée : deux lectures d'horloge, rien d'autre** — `performance.now()` à l'entrée et au `close`,
|
|
443
|
+
quelques dizaines de nanosecondes chacune, deux nombres sur le contexte, aucune structure allouée
|
|
444
|
+
(`Context.ts#Context.stopClock`). C'est un ordre de grandeur de moins que la date et la sérialisation
|
|
445
|
+
JSON de la ligne d'accès qu'elle complète, et c'est trop petit pour qu'un banc de débit le distingue
|
|
446
|
+
de son bruit. Les phases, elles, restent coupées en production.
|
|
374
447
|
- **Trace des frames WS gatée en prod** — `logMessageContent()` court-circuite avant toute construction
|
|
375
|
-
de chaîne hors dev (`WebsocketContext.ts
|
|
448
|
+
de chaîne hors dev (`WebsocketContext.ts#WebsocketContext.logMessageContent`) ; les events lifecycle ne créent aucun `Pdu` en production, et ne sont que DEBUG hors production (`Context.ts#lifecycleEventLogging`).
|
|
376
449
|
|
|
377
450
|
Gate mémoire avant tout commit touchant le pipeline : `npm run test:memory` (skill
|
|
378
451
|
`nodefony-check-memory-health`). Rejouer les chiffres de charge : skill `nodefony-load-test`.
|
|
@@ -387,10 +460,10 @@ Le `requestId` est la **clé de jointure** de l'admin Studio (dev). Les écrans
|
|
|
387
460
|
| **Suivi de requête** (`TraceView`) | `/nodefony/syslog/api/logs/search?requestId=…&order=asc` | Toutes les lignes corrélées + le profil serveur (phases, requêtes SQL). |
|
|
388
461
|
| **Logs** (stream) | data plane syslog | Le flux de logs live, filtrable. |
|
|
389
462
|
| **Audit** | écran Audit | Les événements d'audit persistés. |
|
|
390
|
-
| **Profiler** (dev) | `/nodefony/profiler/api/{requestId}` (`ProfilerAdminApi.ts
|
|
463
|
+
| **Profiler** (dev) | `/nodefony/profiler/api/{requestId}` (`ProfilerAdminApi.ts#createProfilerAdminApi`) | Le profil complet (waterfall des phases) d'une requête donnée. |
|
|
391
464
|
|
|
392
|
-
Le profiler indexe ses instantanés par `requestId` (`Profiler.ts
|
|
393
|
-
`X-Request-Id` de son propre appel AJAX et va chercher le profil (`Profiler.ts
|
|
465
|
+
Le profiler indexe ses instantanés par `requestId` (`Profiler.ts#Profiler.collect`) ; la debug bar lit le
|
|
466
|
+
`X-Request-Id` de son propre appel AJAX et va chercher le profil (`Profiler.ts#ProfilePhase`). Le profiler n'est
|
|
394
467
|
instancié **qu'en dev** (fuite d'info + coût en prod).
|
|
395
468
|
|
|
396
469
|
> [!NOTE]
|
|
@@ -403,28 +476,32 @@ instancié **qu'en dev** (fuite d'info + coût en prod).
|
|
|
403
476
|
<!-- prettier-ignore -->
|
|
404
477
|
| Domaine | Norme | Ancrage |
|
|
405
478
|
| --- | --- | --- |
|
|
406
|
-
| W3C Trace Context (`traceparent`) | W3C Trace Context | `resolveTraceparent()` (`trace.ts
|
|
407
|
-
| Sûreté des valeurs d'en-tête (field-value) | RFC 9110 §5.5 | `sanitizeRequestId()` allowlist (`requestId.ts
|
|
408
|
-
| En-têtes trop volumineux / borne | anti-abus (log flooding) | `MAX_REQUEST_ID_LENGTH` (`requestId.ts
|
|
409
|
-
| Log structuré (PDU, sévérités) | RFC 5424 | `Pdu` + `requestId`/`pid` (`Pdu.ts
|
|
410
|
-
| Sévérité dérivée du statut HTTP | RFC 9110 (catégories) | `severityFromStatus()` (`audit-logger.ts
|
|
411
|
-
|
|
|
479
|
+
| W3C Trace Context (`traceparent`) | W3C Trace Context | `resolveTraceparent()` (`trace.ts#resolveTraceparent`), `parseTraceparent()` (`trace.ts#parseTraceparent`) |
|
|
480
|
+
| Sûreté des valeurs d'en-tête (field-value) | RFC 9110 §5.5 | `sanitizeRequestId()` allowlist (`requestId.ts#sanitizeRequestId`) |
|
|
481
|
+
| En-têtes trop volumineux / borne | anti-abus (log flooding) | `MAX_REQUEST_ID_LENGTH` (`requestId.ts#MAX_REQUEST_ID_LENGTH`) |
|
|
482
|
+
| Log structuré (PDU, sévérités) | RFC 5424 | `Pdu` + `requestId`/`pid` (`Pdu.ts#Pdu.requestId`) |
|
|
483
|
+
| Sévérité dérivée du statut HTTP | RFC 9110 (catégories) | `severityFromStatus()` (`audit-logger.ts#severityFromStatus`) |
|
|
484
|
+
| Durée d'une requête serveur | OpenTelemetry, conventions sémantiques HTTP (`http.server.request.duration`, stable) | fin sur le `close` de la réponse (`Context.ts#Context.stopClock`) |
|
|
485
|
+
| Non-journalisation des secrets | OWASP (logging) | redaction présence-only (`audit-logger.ts#JsonAuditLogger.renderHttp`) |
|
|
412
486
|
|
|
413
487
|
> `X-Request-Id` n'est **pas** un en-tête normalisé (convention de-facto) ; c'est la **valeur** qu'il
|
|
414
488
|
> transporte qui est soumise à la sûreté RFC 9110 §5.5.
|
|
415
489
|
|
|
416
490
|
## ⚠️ Pièges (symptôme → cause → correction)
|
|
417
491
|
|
|
418
|
-
| Symptôme | Cause
|
|
419
|
-
| --------------------------------------------------- |
|
|
420
|
-
| Le `X-Request-Id` que j'envoie n'est pas réfléchi | Valeur non conforme (espace, CR/LF, non-ASCII, > 128) → **rejetée**
|
|
421
|
-
| Les logs de fin de requête n'ont pas de `requestId` | Ils sont émis hors bulle ALS
|
|
422
|
-
| Réponse HTTP/2 sans `x-request-id` | Chemin de réponse h2 distinct du 1.1
|
|
423
|
-
|
|
|
424
|
-
|
|
|
425
|
-
|
|
|
426
|
-
|
|
|
427
|
-
|
|
|
492
|
+
| Symptôme | Cause | Correction |
|
|
493
|
+
| --------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
494
|
+
| Le `X-Request-Id` que j'envoie n'est pas réfléchi | Valeur non conforme (espace, CR/LF, non-ASCII, > 128) → **rejetée** | Utiliser `[A-Za-z0-9._-]{1,128}` (UUID/nanoid/traceparent OK) — sinon UUID serveur |
|
|
495
|
+
| Les logs de fin de requête n'ont pas de `requestId` | Ils sont émis hors bulle ALS | Déjà géré : l'override `log()` rouvre une micro-bulle (`Context.ts#Context.log`) |
|
|
496
|
+
| Réponse HTTP/2 sans `x-request-id` | Chemin de réponse h2 distinct du 1.1 | Déjà géré (`http2/Response.ts#Http2Response.writeHead`) — le port 5152 réfléchit aussi |
|
|
497
|
+
| `durationMs: null` dans chaque ligne d'accès | L'application a posé `timing.enabled: false` (ou `NF__APP__TIMING__ENABLED=false`) | Retirer ce réglage : la durée est mesurée par défaut, production comprise |
|
|
498
|
+
| La durée dépasse largement la somme des phases | Temps passé hors de toute phase : envoi à un client lent, flux encore ouvert | Attendu — la durée va jusqu'au dernier octet remis ; le travail du serveur se lit dans les phases |
|
|
499
|
+
| `phases` absent des lignes de production | Les phases sont réservées au développement (coût par borne) | `NF__APP__TIMING__ENABLED=true` le temps d'un diagnostic, puis le retirer |
|
|
500
|
+
| Pas de `traceparent` renvoyé sur un WebSocket | `ws` n'expose pas l'écriture d'en-tête au handshake | Attendu — la trace WS reste propagée en ALS (`http-kernel.ts#HttpKernel.handleWebsocket`) |
|
|
501
|
+
| Frame WS binaire loggée en `{"0":..,"1":..}` | Sérialisation naïve d'un Buffer | Déjà géré : résumé `[binary N B]` (`wsLogContent.ts#formatWsLogContent`) |
|
|
502
|
+
| Le format de log ne change pas malgré la config | Un `setRequestLogger(...)` programmatique gagne sur la config | L'override est volontaire (last setter wins) — retirer l'appel, ou le régler |
|
|
503
|
+
| Logs d'audit trop volumineux en prod | `stack` sérialisée, ou 100 % des 2xx audités | `includeStack:false` (défaut prod) + `sampleRate` via `setRequestLogger` |
|
|
504
|
+
| `Authorization`/`Cookie` attendus dans le log JSON | Redaction : jamais sérialisés | Par conception — lire les drapeaux `hasAuthorization`/`hasCookie` |
|
|
428
505
|
|
|
429
506
|
## 🧪 Tests & couverture
|
|
430
507
|
|
|
@@ -453,6 +530,7 @@ Skills associés : `nodefony-check-memory-health`, `nodefony-load-test`, `nodefo
|
|
|
453
530
|
|
|
454
531
|
- ⬆️ **Retour au hub** : [@nodefony/http — vue du module](index.md) · [Toute la documentation](../../../../../docs/index.md)
|
|
455
532
|
- 🧭 **Pages sœurs** : [Serveurs](servers.md) · [Sessions](session.md)
|
|
533
|
+
- Brancher un outil de traçage (OpenTelemetry, Datadog) sur le cycle de chaque requête → [Canal de diagnostic](diagnostics-channel.md)
|
|
456
534
|
- Où partent les logs (stdout, fichier, Loki, OpenSearch) + format RFC 5424 → [Journalisation (Syslog)](../../../../nodefony/docs/syslog.md)
|
|
457
535
|
- Le trajet complet d'une requête (où s'insèrent corrélation et trace) → [pipeline-requete](../../../../../docs/architecture/pipeline-requete.md)
|
|
458
536
|
- Sondes de santé et arrêt gracieux → [Serveurs](servers.md)
|