@nodefony/http 10.0.0-alpha.1
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/LICENSE +544 -0
- package/README.md +77 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateParam.js +8 -0
- package/dist/index.js +108 -0
- package/dist/nodefony/command/assetsPublishCommand.js +102 -0
- package/dist/nodefony/command/certificatesCommand.js +47 -0
- package/dist/nodefony/command/networkCommand.js +27 -0
- package/dist/nodefony/command/proxyGenerateCommand.js +66 -0
- package/dist/nodefony/config/config.js +335 -0
- package/dist/nodefony/config/defineModuleConfig.js +93 -0
- package/dist/nodefony/interfaces/IContext.js +1 -0
- package/dist/nodefony/interfaces/ICookie.js +1 -0
- package/dist/nodefony/interfaces/IErrorRenderer.js +1 -0
- package/dist/nodefony/interfaces/IHttpConfig.js +1 -0
- package/dist/nodefony/interfaces/IHttpKernel.js +1 -0
- package/dist/nodefony/interfaces/IRequest.js +1 -0
- package/dist/nodefony/interfaces/IRequestLogger.js +1 -0
- package/dist/nodefony/interfaces/IResponse.js +1 -0
- package/dist/nodefony/interfaces/ISession.js +1 -0
- package/dist/nodefony/interfaces/IUpload.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/service/HttpAdminApi.js +376 -0
- package/dist/nodefony/service/ProfilerAdminApi.js +73 -0
- package/dist/nodefony/service/audit-logger.js +159 -0
- package/dist/nodefony/service/certificates.js +545 -0
- package/dist/nodefony/service/error-renderer.js +320 -0
- package/dist/nodefony/service/http-kernel.js +948 -0
- package/dist/nodefony/service/pretty-request-logger.js +72 -0
- package/dist/nodefony/service/request-logger.js +54 -0
- package/dist/nodefony/service/servers/clientError.js +20 -0
- package/dist/nodefony/service/servers/server-http.js +135 -0
- package/dist/nodefony/service/servers/server-https.js +204 -0
- package/dist/nodefony/service/servers/server-static.js +192 -0
- package/dist/nodefony/service/servers/server-websocket-secure.js +104 -0
- package/dist/nodefony/service/servers/server-websocket.js +104 -0
- package/dist/nodefony/service/servers/serverShutdown.js +31 -0
- package/dist/nodefony/service/servers/wsHeartbeat.js +64 -0
- package/dist/nodefony/service/sessions/sessions-service.js +580 -0
- package/dist/nodefony/service/trace.js +72 -0
- package/dist/nodefony/service/upload/upload-service.js +171 -0
- package/dist/nodefony/src/assets/collectAssets.js +34 -0
- package/dist/nodefony/src/assets/prebuiltUi.js +125 -0
- package/dist/nodefony/src/context/Context.js +415 -0
- package/dist/nodefony/src/context/domainMatcher.js +88 -0
- package/dist/nodefony/src/context/forwarded.js +185 -0
- package/dist/nodefony/src/context/http/HttpContext.js +309 -0
- package/dist/nodefony/src/context/http/Request.js +543 -0
- package/dist/nodefony/src/context/http/Response.js +368 -0
- package/dist/nodefony/src/context/http/parser.js +188 -0
- package/dist/nodefony/src/context/http/urlFastPath.js +103 -0
- package/dist/nodefony/src/context/http2/Request.js +29 -0
- package/dist/nodefony/src/context/http2/Response.js +97 -0
- package/dist/nodefony/src/context/metaData.js +47 -0
- package/dist/nodefony/src/context/requestId.js +41 -0
- package/dist/nodefony/src/context/trustProxy.js +167 -0
- package/dist/nodefony/src/context/websocket/Response.js +181 -0
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +389 -0
- package/dist/nodefony/src/context/websocket/wsBackpressure.js +56 -0
- package/dist/nodefony/src/context/websocket/wsLogContent.js +68 -0
- package/dist/nodefony/src/cookies/cookie.js +258 -0
- package/dist/nodefony/src/errors/httpError.js +69 -0
- package/dist/nodefony/src/profiler/FrameProfile.js +95 -0
- package/dist/nodefony/src/profiler/Profiler.js +139 -0
- package/dist/nodefony/src/proxy/generateProxyConfig.js +157 -0
- package/dist/nodefony/src/rateLimit/IRateLimitStore.js +1 -0
- package/dist/nodefony/src/rateLimit/MemoryRateLimitStore.js +146 -0
- package/dist/nodefony/src/rateLimit/WsConnectionCounter.js +64 -0
- package/dist/nodefony/src/rateLimit/rateLimitFilters.js +20 -0
- package/dist/nodefony/src/servers/portBinder.js +114 -0
- package/dist/nodefony/src/session/session.js +390 -0
- package/dist/nodefony/src/session/storage/MemorySessionStorage.js +185 -0
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +137 -0
- package/dist/nodefony/src/session/storage/sessionFilters.js +83 -0
- package/dist/nodefony/src/session/storage/sessionSort.js +53 -0
- package/dist/types/index.d.ts +83 -0
- package/dist/types/nodefony/command/assetsPublishCommand.d.ts +23 -0
- package/dist/types/nodefony/command/certificatesCommand.d.ts +17 -0
- package/dist/types/nodefony/command/networkCommand.d.ts +8 -0
- package/dist/types/nodefony/command/proxyGenerateCommand.d.ts +19 -0
- package/dist/types/nodefony/config/config.d.ts +197 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +39 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +138 -0
- package/dist/types/nodefony/interfaces/ICookie.d.ts +47 -0
- package/dist/types/nodefony/interfaces/IErrorRenderer.d.ts +55 -0
- package/dist/types/nodefony/interfaces/IHttpConfig.d.ts +12 -0
- package/dist/types/nodefony/interfaces/IHttpKernel.d.ts +10 -0
- package/dist/types/nodefony/interfaces/IRequest.d.ts +35 -0
- package/dist/types/nodefony/interfaces/IRequestLogger.d.ts +31 -0
- package/dist/types/nodefony/interfaces/IResponse.d.ts +39 -0
- package/dist/types/nodefony/interfaces/ISession.d.ts +283 -0
- package/dist/types/nodefony/interfaces/IUpload.d.ts +66 -0
- package/dist/types/nodefony/interfaces/index.d.ts +7 -0
- package/dist/types/nodefony/service/HttpAdminApi.d.ts +18 -0
- package/dist/types/nodefony/service/ProfilerAdminApi.d.ts +23 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +143 -0
- package/dist/types/nodefony/service/certificates.d.ts +246 -0
- package/dist/types/nodefony/service/error-renderer.d.ts +74 -0
- package/dist/types/nodefony/service/http-kernel.d.ts +377 -0
- package/dist/types/nodefony/service/pretty-request-logger.d.ts +25 -0
- package/dist/types/nodefony/service/request-logger.d.ts +18 -0
- package/dist/types/nodefony/service/servers/clientError.d.ts +14 -0
- package/dist/types/nodefony/service/servers/server-http.d.ts +42 -0
- package/dist/types/nodefony/service/servers/server-https.d.ts +41 -0
- package/dist/types/nodefony/service/servers/server-static.d.ts +62 -0
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +29 -0
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +29 -0
- package/dist/types/nodefony/service/servers/serverShutdown.d.ts +27 -0
- package/dist/types/nodefony/service/servers/wsHeartbeat.d.ts +46 -0
- package/dist/types/nodefony/service/sessions/sessions-service.d.ts +218 -0
- package/dist/types/nodefony/service/trace.d.ts +39 -0
- package/dist/types/nodefony/service/upload/upload-service.d.ts +61 -0
- package/dist/types/nodefony/src/assets/collectAssets.d.ts +35 -0
- package/dist/types/nodefony/src/assets/prebuiltUi.d.ts +99 -0
- package/dist/types/nodefony/src/context/Context.d.ts +195 -0
- package/dist/types/nodefony/src/context/domainMatcher.d.ts +67 -0
- package/dist/types/nodefony/src/context/forwarded.d.ts +95 -0
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +85 -0
- package/dist/types/nodefony/src/context/http/Request.d.ts +203 -0
- package/dist/types/nodefony/src/context/http/Response.d.ts +68 -0
- package/dist/types/nodefony/src/context/http/parser.d.ts +65 -0
- package/dist/types/nodefony/src/context/http/urlFastPath.d.ts +52 -0
- package/dist/types/nodefony/src/context/http2/Request.d.ts +14 -0
- package/dist/types/nodefony/src/context/http2/Response.d.ts +20 -0
- package/dist/types/nodefony/src/context/metaData.d.ts +58 -0
- package/dist/types/nodefony/src/context/requestId.d.ts +28 -0
- package/dist/types/nodefony/src/context/trustProxy.d.ts +77 -0
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +53 -0
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +125 -0
- package/dist/types/nodefony/src/context/websocket/wsBackpressure.d.ts +73 -0
- package/dist/types/nodefony/src/context/websocket/wsLogContent.d.ts +37 -0
- package/dist/types/nodefony/src/cookies/cookie.d.ts +88 -0
- package/dist/types/nodefony/src/errors/httpError.d.ts +15 -0
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +110 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +192 -0
- package/dist/types/nodefony/src/proxy/generateProxyConfig.d.ts +76 -0
- package/dist/types/nodefony/src/rateLimit/IRateLimitStore.d.ts +98 -0
- package/dist/types/nodefony/src/rateLimit/MemoryRateLimitStore.d.ts +40 -0
- package/dist/types/nodefony/src/rateLimit/WsConnectionCounter.d.ts +37 -0
- package/dist/types/nodefony/src/rateLimit/rateLimitFilters.d.ts +18 -0
- package/dist/types/nodefony/src/servers/portBinder.d.ts +102 -0
- package/dist/types/nodefony/src/session/session.d.ts +171 -0
- package/dist/types/nodefony/src/session/storage/MemorySessionStorage.d.ts +77 -0
- package/dist/types/nodefony/src/session/storage/RevocationGuardStorage.d.ts +81 -0
- package/dist/types/nodefony/src/session/storage/sessionFilters.d.ts +102 -0
- package/dist/types/nodefony/src/session/storage/sessionSort.d.ts +45 -0
- package/docs/cookies.md +365 -0
- package/docs/index.md +163 -0
- package/docs/observabilite.md +460 -0
- package/docs/rate-limit.md +372 -0
- package/docs/servers.md +935 -0
- package/docs/session.md +768 -0
- package/docs/upload.md +460 -0
- package/package.json +101 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Observabilité HTTP — journalisation de requête, corrélation, trace"
|
|
3
|
+
navTitle: Observabilité HTTP
|
|
4
|
+
lang: fr
|
|
5
|
+
module: "@nodefony/http"
|
|
6
|
+
topic: observabilite
|
|
7
|
+
section: "Cœur runtime"
|
|
8
|
+
audience: [developer, devops]
|
|
9
|
+
tags:
|
|
10
|
+
[
|
|
11
|
+
observabilite,
|
|
12
|
+
log,
|
|
13
|
+
requestId,
|
|
14
|
+
correlation,
|
|
15
|
+
trace,
|
|
16
|
+
traceparent,
|
|
17
|
+
audit,
|
|
18
|
+
websocket,
|
|
19
|
+
]
|
|
20
|
+
version: "doc"
|
|
21
|
+
status: stable
|
|
22
|
+
updated: 2026-07-21
|
|
23
|
+
source: "src/packages/@nodefony/http/docs/observabilite.md"
|
|
24
|
+
coverageModule: http
|
|
25
|
+
coverageFiles: request-logger.ts,pretty-request-logger.ts,audit-logger.ts,requestId.ts,trace.ts,wsLogContent.ts
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
# Observabilité HTTP — journalisation de requête, corrélation, trace
|
|
29
|
+
|
|
30
|
+
> Ce que le module produit pour **voir** ce qui traverse le serveur : une ligne de log par requête
|
|
31
|
+
> (méthode, statut, durée, taille), un **identifiant de corrélation** (`requestId`) qui relie toutes
|
|
32
|
+
> les lignes d'une même requête — HTTP comme WebSocket —, et une **trace** W3C (`traceparent`) qui
|
|
33
|
+
> franchit les frontières de service. Cette page décrit ce que `@nodefony/http` **émet** ; pour savoir
|
|
34
|
+
> **où** partent ces lignes (stdout, fichier, Loki, OpenSearch), voir la page Syslog du cœur.
|
|
35
|
+
|
|
36
|
+
📍 [Documentation](../../../../../docs/index.md) › [@nodefony/http](index.md) › **Observabilité**
|
|
37
|
+
|
|
38
|
+
## 🧠 Le modèle mental — un fil rouge par requête
|
|
39
|
+
|
|
40
|
+
Le cœur de l'observabilité ici n'est pas « écrire des logs » — c'est **corréler**. Chaque requête reçoit
|
|
41
|
+
un `requestId` dès son entrée ; ce fil est ensuite **teinté** dans chaque ligne de log qu'elle produit,
|
|
42
|
+
partout dans la pile async, sans qu'on ait à le passer d'appel en appel. Le débogage cesse d'être « quelle
|
|
43
|
+
ligne appartient à quelle requête ? ».
|
|
44
|
+
|
|
45
|
+
```mermaid
|
|
46
|
+
flowchart TD
|
|
47
|
+
CL["Client<br/>(X-Request-Id ? traceparent ?)"] --> ENTRY["Entrée requête<br/>HttpContext / WebsocketContext"]
|
|
48
|
+
ENTRY --> ID{"X-Request-Id<br/>sûr ?"}
|
|
49
|
+
ID -->|"oui"| ADOPT["adopte la valeur cliente"]
|
|
50
|
+
ID -->|"non / absent"| GEN["génère un UUID v4"]
|
|
51
|
+
ADOPT --> ALS
|
|
52
|
+
GEN --> ALS["Bulle ALS<br/>RequestContext.run({ requestId, traceparent })"]
|
|
53
|
+
ALS --> LOGS["chaque Pdu créé dans la bulle<br/>est tagué requestId"]
|
|
54
|
+
ALS --> CTRL["controller / services<br/>lisent requestId (ALS)"]
|
|
55
|
+
ENTRY --> RESP["Réponse HTTP<br/>echo X-Request-Id + traceparent"]
|
|
56
|
+
ENTRY --> LINE["1 ligne de bilan par requête<br/>IRequestLogger (pretty | json | default)"]
|
|
57
|
+
LINE --> SYS["Syslog (cœur)<br/>ring + drivers → stdout/file/loki/opensearch"]
|
|
58
|
+
LOGS --> SYS
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Trois idées portent tout le reste :
|
|
62
|
+
|
|
63
|
+
1. **Le `requestId` est le fil rouge.** Généré à l'entrée, adopté du client s'il est sûr, propagé par
|
|
64
|
+
`AsyncLocalStorage` (ALS), réfléchi au client, écrit dans chaque log.
|
|
65
|
+
2. **Le format de la ligne de bilan est un choix d'opérateur.** Un même contrat (`IRequestLogger`) a
|
|
66
|
+
trois implémentations livrées : verbeux (défaut), joli (dev), JSON (prod).
|
|
67
|
+
3. **Le module émet, Syslog achemine.** Ce module fabrique le contenu (ligne, `requestId`, `traceparent`)
|
|
68
|
+
et le remet à `context.log()` ; le **backplane Syslog** décide où il part (page dédiée).
|
|
69
|
+
|
|
70
|
+
## 📖 Lexique
|
|
71
|
+
|
|
72
|
+
| Terme | Sens |
|
|
73
|
+
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
74
|
+
| `requestId` | Identifiant unique d'une requête (UUID v4 par défaut). Corrèle toutes ses lignes de log. Stable de bout en bout. |
|
|
75
|
+
| `X-Request-Id` | En-tête HTTP de **convention** (de-facto, non normalisé) portant le `requestId` : le client peut l'imposer, le serveur le réfléchit. |
|
|
76
|
+
| Corrélation | Relier des lignes de log éparses à la **même** requête via une clé partagée (`requestId`, `pid`). |
|
|
77
|
+
| ALS | _AsyncLocalStorage_ : mémoire Node attachée au contexte d'exécution async — propage `requestId` sans le passer en argument. |
|
|
78
|
+
| `RequestContext` | Façade Nodefony au-dessus de l'ALS : `getRequestId()`, `getUser()`, `getUserId()`, `traceparent`. |
|
|
79
|
+
| `Pdu` | _Process Data Unit_ : une entrée de log structurée (RFC 5424). Porte `requestId` + `pid`. |
|
|
80
|
+
| Syslog | Le hub de logs du cœur (ring buffer + drivers). Reçoit chaque `Pdu` — **page distincte**. |
|
|
81
|
+
| `traceparent` | En-tête **W3C Trace Context** : `version-traceId-spanId-flags`. Corrèle une requête à travers plusieurs services. |
|
|
82
|
+
| traceId / spanId | Identifiant global d'une trace (16 o) / d'un maillon (8 o) au sein de cette trace. |
|
|
83
|
+
| Access log | Ligne récapitulative émise **en fin** de requête (méthode, statut, durée, `requestId`). |
|
|
84
|
+
| Audit log | Access log au format JSON canonique (1 objet/requête), destiné à l'ingestion machine (Loki/ELK/OTel). |
|
|
85
|
+
| Redaction | Ne jamais journaliser un secret : `Authorization`/`Cookie` sont réduits à des **drapeaux de présence**. |
|
|
86
|
+
| Sampling | N'écrire qu'une requête nominale sur N (2xx/3xx), en gardant **toutes** les erreurs — levier perf du hot path. |
|
|
87
|
+
| Phases / timing | Découpage chronométré du pipeline (`resolve`, `action`…) ; alimente la durée et le waterfall du Suivi de requête. |
|
|
88
|
+
|
|
89
|
+
## Qu'est-ce que l'observabilité d'une requête ?
|
|
90
|
+
|
|
91
|
+
Imagine un colis dans un centre de tri. Sans **numéro de suivi**, chaque tapis roulant note « un colis
|
|
92
|
+
est passé » — mais personne ne peut reconstituer le trajet d'UN colis précis. Le `requestId` est ce
|
|
93
|
+
numéro de suivi : collé à l'entrée, il apparaît sur chaque scan, si bien qu'une recherche par numéro
|
|
94
|
+
rassemble toute l'histoire de la requête — l'authentification, la requête SQL lente, l'erreur finale.
|
|
95
|
+
|
|
96
|
+
Concrètement, l'observabilité de la couche HTTP répond à trois questions, et rien d'autre :
|
|
97
|
+
|
|
98
|
+
1. **Que s'est-il passé ?** — une ligne par requête : `GET 200 /api/x 12.3ms 127.0.0.1 [a1b2c3d4]`.
|
|
99
|
+
2. **Ces lignes vont-elles ensemble ?** — le `requestId` les relie ; le `pid` dit quel worker.
|
|
100
|
+
3. **Cette requête vient-elle d'ailleurs ?** — le `traceparent` W3C rattache la requête à une trace
|
|
101
|
+
distribuée initiée par un service amont.
|
|
102
|
+
|
|
103
|
+
> [!NOTE]
|
|
104
|
+
> Cette page couvre ce que le module **produit**. La **destination** des logs (stdout cloud-native,
|
|
105
|
+
> fichier, Grafana Loki, OpenSearch) et le format RFC 5424 du `Pdu` appartiennent au backplane Syslog du
|
|
106
|
+
> cœur → [Journalisation (Syslog)](../../../../nodefony/docs/syslog.md).
|
|
107
|
+
|
|
108
|
+
## La vision Nodefony
|
|
109
|
+
|
|
110
|
+
Le différenciateur — **HTTP et WebSocket dans le même pipeline** — se retrouve dans l'observabilité :
|
|
111
|
+
un `requestId`, un `traceparent` et un contrat de logger **uniques** couvrent les deux transports.
|
|
112
|
+
|
|
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:244`), voyage dans l'ALS via `RequestContext.run(...)`
|
|
115
|
+
(`http-kernel.ts:1300` pour HTTP, `http-kernel.ts:1590` pour WS), et se lit de n'importe où avec
|
|
116
|
+
`RequestContext.getRequestId()` — un controller, un service, un adapter ORM, sans jamais le threader.
|
|
117
|
+
|
|
118
|
+
**La ligne de bilan est branchable.** Le kernel ne code pas un format en dur : il consulte un
|
|
119
|
+
`IRequestLogger` (`IRequestLogger.ts:25`) résolu au boot depuis la config (`applyRequestLoggerFromConfig`,
|
|
120
|
+
`http-kernel.ts:660`), remplaçable à chaud par `httpKernel.setRequestLogger(...)` (`http-kernel.ts:866`).
|
|
121
|
+
Trois formateurs sont livrés ; un quatrième maison s'écrit en implémentant l'interface.
|
|
122
|
+
|
|
123
|
+
**Zero Trust sur l'entrée cliente.** Un `X-Request-Id` fourni par le client finit réfléchi en réponse,
|
|
124
|
+
écrit dans les logs et propagé en ALS — donc une valeur non assainie ouvrirait log-injection (CR/LF) et
|
|
125
|
+
throw natif de `setHeader`. Nodefony **valide puis adopte, ou rejette** (`sanitizeRequestId`,
|
|
126
|
+
`requestId.ts:38`) : jamais nettoyer (masquerait l'abus), toujours retomber sur l'UUID serveur.
|
|
127
|
+
|
|
128
|
+
## 🚀 Démarrage rapide
|
|
129
|
+
|
|
130
|
+
Dans une application générée par `nodefony create app`, **l'observabilité est déjà là** : chaque requête
|
|
131
|
+
a son `requestId`, chaque réponse le réfléchit, chaque log le porte. On n'écrit que ses **écarts** — le
|
|
132
|
+
format de ligne — puis on lit le `requestId` là où on en a besoin.
|
|
133
|
+
|
|
134
|
+
### 1. Choisir le format des lignes de log
|
|
135
|
+
|
|
136
|
+
Le format est un choix d'opérateur (config d'app). Défaut `auto` : joli en dev, JSON en prod.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// nodefony.config.ts — le format des lignes de log de requête
|
|
140
|
+
export default defineConfig((ctx) => ({
|
|
141
|
+
log: {
|
|
142
|
+
// "auto" (défaut) = pretty en dev / json en prod. On force ici JSON en prod
|
|
143
|
+
// pour un pipeline d'ingestion (Loki/ELK) qui parse 1 objet par requête.
|
|
144
|
+
requestFormat: ctx.isProd ? "json" : "pretty",
|
|
145
|
+
},
|
|
146
|
+
modules: ["@nodefony/http", "@nodefony/framework"],
|
|
147
|
+
}));
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 2. Lire le `requestId` dans un contrôleur
|
|
151
|
+
|
|
152
|
+
Rien à câbler : le `requestId` est déjà sur le contexte, et déjà réfléchi au client.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// nodefony/controller/TraceController.ts — complet, compile tel quel
|
|
156
|
+
import { Controller, controller, Get } from "@nodefony/framework";
|
|
157
|
+
import type { Context } from "@nodefony/http";
|
|
158
|
+
|
|
159
|
+
@controller("/trace")
|
|
160
|
+
class TraceController extends Controller {
|
|
161
|
+
constructor(context: Context) {
|
|
162
|
+
super("TraceController", context);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@Get("/whoami")
|
|
166
|
+
async whoami() {
|
|
167
|
+
// Corrèle CETTE requête à toutes ses lignes de log ; aussi réfléchi au
|
|
168
|
+
// client dans l'en-tête `X-Request-Id` de la réponse.
|
|
169
|
+
const requestId = this.context?.requestId;
|
|
170
|
+
this.log("handler atteint", "INFO"); // ← cette ligne portera le même requestId
|
|
171
|
+
return this.renderJson({ requestId });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export default TraceController;
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 3. Corréler un log métier hors du contrôleur (ALS)
|
|
179
|
+
|
|
180
|
+
Un service profond n'a pas le contexte sous la main. Il lit le `requestId` dans l'ALS — même valeur,
|
|
181
|
+
zéro argument à faire transiter.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// nodefony/service/AuditTrail.ts — corréler un log métier via l'ALS
|
|
185
|
+
import { Service, RequestContext } from "nodefony";
|
|
186
|
+
|
|
187
|
+
class AuditTrail extends Service {
|
|
188
|
+
record(action: string): void {
|
|
189
|
+
// Même requestId que le controller, lu depuis l'AsyncLocalStorage.
|
|
190
|
+
const requestId = RequestContext.getRequestId();
|
|
191
|
+
this.log(`action=${action} req=${requestId ?? "-"}`, "NOTICE");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export default AuditTrail;
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 4. Observer — la corrélation de bout en bout
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# On impose notre propre requestId (client) — le serveur le réfléchit s'il est sûr.
|
|
202
|
+
curl -si http://127.0.0.1:5151/trace/whoami -H 'X-Request-Id: demo-abc-123' | grep -i x-request-id
|
|
203
|
+
# x-request-id: demo-abc-123
|
|
204
|
+
|
|
205
|
+
# Sans en-tête : le serveur génère un UUID v4 et le renvoie.
|
|
206
|
+
curl -si http://127.0.0.1:5151/trace/whoami | grep -i x-request-id
|
|
207
|
+
# x-request-id: 6f1c0d2e-...-...
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Côté serveur, en dev (format `pretty`), les lignes de la même requête partagent le `[demo-abc]` :
|
|
211
|
+
|
|
212
|
+
```text
|
|
213
|
+
INFO handler atteint [demo-abc]
|
|
214
|
+
GET 200 /trace/whoami 3.1ms 127.0.0.1 [demo-abc]
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
> [!TIP]
|
|
218
|
+
> Une valeur cliente **non sûre** (espace, CR/LF, non-ASCII, > 128 car.) est **rejetée** : le serveur
|
|
219
|
+
> garde son UUID et le renvoie. C'est voulu — voir Sécurité.
|
|
220
|
+
|
|
221
|
+
## 🏗️ Architecture interne
|
|
222
|
+
|
|
223
|
+
### Le `requestId` — génération, adoption, réflexion
|
|
224
|
+
|
|
225
|
+
| Étape | Où | Comportement |
|
|
226
|
+
| ------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
227
|
+
| Génération | `Context.requestId = randomUUID()` (`Context.ts:244`) | UUID v4 posé dans le constructeur de base — HTTP **et** WS. |
|
|
228
|
+
| Adoption HTTP | `sanitizeRequestId(headers["x-request-id"])` (`HttpContext.ts:158`) | Remplace l'UUID **si** la valeur cliente est sûre, sinon on garde l'UUID. |
|
|
229
|
+
| Adoption WS | `sanitizeRequestId(...)` au handshake (`WebsocketContext.ts:139`) | 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:153`) | Écrit dans `writeHead()`, sur **chaque** réponse. |
|
|
231
|
+
| Réflexion HTTP/2 | `this.headers["x-request-id"] = requestId` (`http2/Response.ts:71`) | Sinon les réponses du port 5152 sortiraient sans corrélation. |
|
|
232
|
+
| ALS (HTTP) | `RequestContext.run({ requestId, … })` (`http-kernel.ts:431`) | Ouvre la bulle → tout `Pdu` créé dedans est tagué. |
|
|
233
|
+
| ALS (WS) | `RequestContext.run({ requestId, … })` (`http-kernel.ts:431`) | Handshake **et** messages (via `AsyncResource.bind`, BUG-001). |
|
|
234
|
+
| Capture dans le log | `Pdu.requestId = Pdu.requestIdProvider?.()` (`Pdu.ts:221`) | Provider injectable branché sur l'ALS côté Node — 0 lecture côté navigateur. |
|
|
235
|
+
|
|
236
|
+
> [!IMPORTANT]
|
|
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:459`) rouvre alors une micro-bulle depuis
|
|
239
|
+
> `this.requestId` pour que le `Pdu` capture quand même la corrélation — sinon la ligne d'entrée d'une
|
|
240
|
+
> trace serait la seule à ne PAS porter son `requestId`.
|
|
241
|
+
|
|
242
|
+
### La trace W3C — `traceparent`
|
|
243
|
+
|
|
244
|
+
Nodefony implémente **W3C Trace Context** (le code s'y réfère explicitement, `trace.ts:1`). À l'entrée :
|
|
245
|
+
|
|
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:83`).
|
|
248
|
+
- Absent ou malformé → on **forge** une trace neuve (`00-<traceId>-<spanId>-01`, échantillonnée).
|
|
249
|
+
- La validation refuse `version=ff` et un `traceId`/`spanId` tout-à-zéro — `parseTraceparent()`
|
|
250
|
+
(`trace.ts:38`), conforme à la spec (le récepteur NE DOIT PAS propager ces valeurs).
|
|
251
|
+
|
|
252
|
+
Le `traceparent` résolu est propagé en ALS **et** réfléchi sur la réponse HTTP (`context/http/Response.ts:435`). Côté
|
|
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:1419`) ; la corrélation reste visible côté serveur.
|
|
255
|
+
|
|
256
|
+
### Le contrat de logger — `IRequestLogger`
|
|
257
|
+
|
|
258
|
+
Le kernel tient un `IRequestLogger` singleton (`http-kernel.ts:270`) et lui délègue le rendu de la ligne
|
|
259
|
+
de bilan, au teardown, via `Context.logRequest()` (`Context.ts:595`) côté HTTP et
|
|
260
|
+
`WebsocketContext.logRequest()` (`WebsocketContext.ts:209`) côté WS. Le contrat a trois méthodes
|
|
261
|
+
(`IRequestLogger.ts:25`) :
|
|
262
|
+
|
|
263
|
+
- `renderHttp(context, error?)` → `{ text, severity, msgid }` remis à `context.log()`.
|
|
264
|
+
- `renderWebsocket(context, error?, acceptedProtocol?)` → idem pour le WS.
|
|
265
|
+
- `shouldSample?(context, error?)` — **portillon** évalué AVANT le rendu : `false` = ligne sautée sans
|
|
266
|
+
aucune allocation ni `JSON.stringify` (levier perf du logger d'audit).
|
|
267
|
+
|
|
268
|
+
### La trace des frames WebSocket
|
|
269
|
+
|
|
270
|
+
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:55`), appelé par
|
|
272
|
+
`WebsocketContext.logMessageContent()` (`WebsocketContext.ts:397`) :
|
|
273
|
+
|
|
274
|
+
- `string` → tronquée à `WS_LOG_CONTENT_CAP` (4096, `wsLogContent.ts:15`) + ellipse.
|
|
275
|
+
- **binaire** (Buffer, ArrayBuffer, TypedArray, Blob, `Buffer[]`) → résumé `[binary N B]`, **jamais**
|
|
276
|
+
sérialisé (`binaryByteLength()`, `wsLogContent.ts:27`) — un dump `{"0":..,"1":..}` serait énorme et faux.
|
|
277
|
+
- objet « JSON » → `JSON.stringify` compact tronqué, repli `String(...)` sur cycle/`bigint`.
|
|
278
|
+
|
|
279
|
+
Le tout est **gaté hors production** : `logMessageContent` court-circuite avant toute construction de
|
|
280
|
+
chaîne quand l'env est `production` (`WebsocketContext.ts:401`) → 0 surcoût sur le hot path WS.
|
|
281
|
+
|
|
282
|
+
## ⚙️ Configuration
|
|
283
|
+
|
|
284
|
+
Un seul réglage d'app, côté cœur (bloc `log`), pilote le format des lignes.
|
|
285
|
+
|
|
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:39`). |
|
|
289
|
+
|
|
290
|
+
`auto` est résolu au boot selon l'environnement (`applyRequestLoggerFromConfig`, `http-kernel.ts:660`) :
|
|
291
|
+
|
|
292
|
+
| Valeur | Formateur | Rendu |
|
|
293
|
+
| --------- | ---------------------- | ----------------------------------------------------------------------------------------- |
|
|
294
|
+
| `pretty` | `PrettyRequestLogger` | 1 ligne colorée `GET 200 /x 12.3ms 127.0.0.1 [a1b2c3d4]` (`pretty-request-logger.ts:34`). |
|
|
295
|
+
| `json` | `JsonAuditLogger` | 1 objet JSON canonique par requête (`audit-logger.ts:122`). |
|
|
296
|
+
| `default` | `DefaultRequestLogger` | Format legacy verbeux `URL : … FROM : … ID : <uuid>` (`request-logger.ts:21`). |
|
|
297
|
+
|
|
298
|
+
> [!NOTE]
|
|
299
|
+
> Le **réglage fin** du logger JSON (`sampleRate`, `includeStack`, `maxCauseDepth`, `nominal`) n'est pas
|
|
300
|
+
> un champ déclaré du schéma d'app : il se pose **programmatiquement**, en construisant le logger et en
|
|
301
|
+
> l'injectant — `httpKernel.setRequestLogger(new JsonAuditLogger({ sampleRate: 10 }))` (options :
|
|
302
|
+
> `JsonAuditLoggerOptions`, `audit-logger.ts:78`). L'override programmatique gagne toujours sur la config.
|
|
303
|
+
|
|
304
|
+
## 🧰 Les trois formateurs (et le contrat)
|
|
305
|
+
|
|
306
|
+
Choisir en cinq secondes :
|
|
307
|
+
|
|
308
|
+
| Formateur | Quand | Coût | Sortie |
|
|
309
|
+
| ---------------------- | ---------------------- | -------------------------------------- | -------------------------------------- |
|
|
310
|
+
| `PrettyRequestLogger` | Développement | quelques strings/req (chemin terminal) | 1 ligne colorée lisible à l'œil. |
|
|
311
|
+
| `JsonAuditLogger` | Production / ingestion | 1 objet + 1 `JSON.stringify`/req | 1 PDU JSON parsable par Loki/ELK/OTel. |
|
|
312
|
+
| `DefaultRequestLogger` | Compat legacy | 0 allocation (singleton stateless) | Format historique coloré multi-champs. |
|
|
313
|
+
|
|
314
|
+
### `PrettyRequestLogger` — une ligne pour l'humain
|
|
315
|
+
|
|
316
|
+
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:100`) et `requestId` tronqué aux 8
|
|
318
|
+
premiers caractères (`shortId()`, `pretty-request-logger.ts:116`). La durée est dérivée des phases de
|
|
319
|
+
timing (`pretty-request-logger.ts:121`).
|
|
320
|
+
|
|
321
|
+
### `JsonAuditLogger` — un PDU par requête, pour la machine
|
|
322
|
+
|
|
323
|
+
Émet un `AuditLogEntry` canonique (`audit-logger.ts:28`) : `ts`, `requestId`, `userId`, `type`, `method`,
|
|
324
|
+
`url`, `status`, `durationMs`, `remoteAddress`, `host`, `userAgent`, phases, erreur enrichie (nom, code,
|
|
325
|
+
`errorType`, `cause` bornée à 5, stack **dev seulement**). Deux propriétés majeures :
|
|
326
|
+
|
|
327
|
+
- **Redaction par construction** : `Authorization` et `Cookie` ne sont **jamais** sérialisés — seuls des
|
|
328
|
+
drapeaux `hasAuthorization`/`hasCookie` le sont (`audit-logger.ts:211`).
|
|
329
|
+
- **Sampling déterministe** : `shouldSample()` (`audit-logger.ts:156`) garde 1 requête nominale sur N mais
|
|
330
|
+
**jamais** une erreur ni un `status >= 400` — on ne perd aucun échec ; compteur, pas de RNG.
|
|
331
|
+
|
|
332
|
+
### `DefaultRequestLogger` — le format legacy
|
|
333
|
+
|
|
334
|
+
Singleton sans état, 0 allocation par requête (`request-logger.ts:21`). Conserve le format historique
|
|
335
|
+
`URL : … FROM : … ORIGIN : … ID : <uuid>`, avec `Accept-Protocol` en plus côté WebSocket.
|
|
336
|
+
|
|
337
|
+
### Écrire son propre formateur
|
|
338
|
+
|
|
339
|
+
Implémenter `IRequestLogger` (`IRequestLogger.ts:25`) et l'injecter — NCSA Common Log Format, syslog RFC
|
|
340
|
+
5424 texte, OpenTelemetry logs… `httpKernel.setRequestLogger(monLogger)` (`http-kernel.ts:866`). Les trois
|
|
341
|
+
formateurs et le type sont exportés depuis `@nodefony/http` (`index.ts:221`).
|
|
342
|
+
|
|
343
|
+
## 🔐 Sécurité
|
|
344
|
+
|
|
345
|
+
Le `requestId` client est le seul intrant **non fiable** de cette page, et il touche trois surfaces
|
|
346
|
+
sensibles à la fois — d'où une validation stricte.
|
|
347
|
+
|
|
348
|
+
<!-- prettier-ignore -->
|
|
349
|
+
| Menace | Vecteur | Défense |
|
|
350
|
+
| --- | --- | --- |
|
|
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:26`) — CR/LF exclus. |
|
|
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:38`). |
|
|
353
|
+
| Log flooding | `X-Request-Id` géant | Borne `MAX_REQUEST_ID_LENGTH = 128` (`requestId.ts:18`). |
|
|
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:211`). |
|
|
355
|
+
| Fuite de stack en prod | `error.stack` dans les logs d'audit publics | `includeStack` par défaut `false` en production (`audit-logger.ts:133`). |
|
|
356
|
+
|
|
357
|
+
> [!WARNING]
|
|
358
|
+
> 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:31`).
|
|
360
|
+
|
|
361
|
+
## ⚡ Performance & mémoire
|
|
362
|
+
|
|
363
|
+
La ligne de bilan et la corrélation sont sur le **chemin chaud** : leur coût est multiplié par le RPS.
|
|
364
|
+
Les choix visibles dans le code :
|
|
365
|
+
|
|
366
|
+
- **`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:66`).
|
|
368
|
+
- **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:169`) → 0 lecture, 0 allocation.
|
|
370
|
+
- **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:156`).
|
|
372
|
+
- **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:104`).
|
|
374
|
+
- **Trace des frames WS gatée en prod** — `logMessageContent()` court-circuite avant toute construction
|
|
375
|
+
de chaîne hors dev (`WebsocketContext.ts:401`) ; les events lifecycle ne créent aucun `Pdu` en production (`Context.ts:67`).
|
|
376
|
+
|
|
377
|
+
Gate mémoire avant tout commit touchant le pipeline : `npm run test:memory` (skill
|
|
378
|
+
`nodefony-check-memory-health`). Rejouer les chiffres de charge : skill `nodefony-load-test`.
|
|
379
|
+
|
|
380
|
+
## 📡 Observabilité — Studio
|
|
381
|
+
|
|
382
|
+
Le `requestId` est la **clé de jointure** de l'admin Studio (dev). Les écrans et le data plane :
|
|
383
|
+
|
|
384
|
+
<!-- prettier-ignore -->
|
|
385
|
+
| Surface | Route / endpoint | Ce qu'on y voit |
|
|
386
|
+
| --- | --- | --- |
|
|
387
|
+
| **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
|
+
| **Logs** (stream) | data plane syslog | Le flux de logs live, filtrable. |
|
|
389
|
+
| **Audit** | écran Audit | Les événements d'audit persistés. |
|
|
390
|
+
| **Profiler** (dev) | `/nodefony/profiler/api/{requestId}` (`ProfilerAdminApi.ts:23`) | Le profil complet (waterfall des phases) d'une requête donnée. |
|
|
391
|
+
|
|
392
|
+
Le profiler indexe ses instantanés par `requestId` (`Profiler.ts:203`) ; la debug bar lit le
|
|
393
|
+
`X-Request-Id` de son propre appel AJAX et va chercher le profil (`Profiler.ts:14`). Le profiler n'est
|
|
394
|
+
instancié **qu'en dev** (fuite d'info + coût en prod).
|
|
395
|
+
|
|
396
|
+
> [!NOTE]
|
|
397
|
+
> Les **sondes de santé** `/livez` / `/readyz` court-circuitent le pipeline : elles n'émettent **aucune**
|
|
398
|
+
> ligne de log par sonde (un journal par battement du kubelet serait un amplificateur). Détail dans
|
|
399
|
+
> [Serveurs](servers.md).
|
|
400
|
+
|
|
401
|
+
## 📜 Normes appliquées
|
|
402
|
+
|
|
403
|
+
<!-- prettier-ignore -->
|
|
404
|
+
| Domaine | Norme | Ancrage |
|
|
405
|
+
| --- | --- | --- |
|
|
406
|
+
| W3C Trace Context (`traceparent`) | W3C Trace Context | `resolveTraceparent()` (`trace.ts:83`), `parseTraceparent()` (`trace.ts:38`) |
|
|
407
|
+
| Sûreté des valeurs d'en-tête (field-value) | RFC 9110 §5.5 | `sanitizeRequestId()` allowlist (`requestId.ts:26`) |
|
|
408
|
+
| En-têtes trop volumineux / borne | anti-abus (log flooding) | `MAX_REQUEST_ID_LENGTH` (`requestId.ts:18`) |
|
|
409
|
+
| Log structuré (PDU, sévérités) | RFC 5424 | `Pdu` + `requestId`/`pid` (`Pdu.ts:157`) |
|
|
410
|
+
| Sévérité dérivée du statut HTTP | RFC 9110 (catégories) | `severityFromStatus()` (`audit-logger.ts:71`) |
|
|
411
|
+
| Non-journalisation des secrets | OWASP (logging) | redaction présence-only (`audit-logger.ts:211`) |
|
|
412
|
+
|
|
413
|
+
> `X-Request-Id` n'est **pas** un en-tête normalisé (convention de-facto) ; c'est la **valeur** qu'il
|
|
414
|
+
> transporte qui est soumise à la sûreté RFC 9110 §5.5.
|
|
415
|
+
|
|
416
|
+
## ⚠️ Pièges (symptôme → cause → correction)
|
|
417
|
+
|
|
418
|
+
| Symptôme | Cause | Correction |
|
|
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** | Utiliser `[A-Za-z0-9._-]{1,128}` (UUID/nanoid/traceparent OK) — sinon UUID serveur |
|
|
421
|
+
| 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:459`) |
|
|
422
|
+
| Réponse HTTP/2 sans `x-request-id` | Chemin de réponse h2 distinct du 1.1 | Déjà géré (`http2/Response.ts:71`) — le port 5152 réfléchit aussi |
|
|
423
|
+
| 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:1296`) |
|
|
424
|
+
| 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:63`) |
|
|
425
|
+
| 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 |
|
|
426
|
+
| 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` |
|
|
427
|
+
| `Authorization`/`Cookie` attendus dans le log JSON | Redaction : jamais sérialisés | Par conception — lire les drapeaux `hasAuthorization`/`hasCookie` |
|
|
428
|
+
|
|
429
|
+
## 🧪 Tests & couverture
|
|
430
|
+
|
|
431
|
+
Les chiffres exacts vivent dans la carte de tests de cette page (régénérée depuis vitest, jamais figés
|
|
432
|
+
dans le Markdown).
|
|
433
|
+
|
|
434
|
+
| Type | Où |
|
|
435
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
436
|
+
| Unitaire — `requestId` | `unit/requestId.test.ts` — allowlist Zero Trust (CR/LF, non-ASCII, longueur, rejet vs nettoyage) |
|
|
437
|
+
| Unitaire — formateurs | `unit/RequestLogger.test.ts` (format legacy), `unit/PrettyRequestLogger.test.ts` (ligne colorée + durée) |
|
|
438
|
+
| Unitaire — audit | `unit/AuditLogger.test.ts` — forme JSON, redaction, sévérité par statut, sampling, `cause` |
|
|
439
|
+
| Unitaire — trace WS | `unit/wsLogContent.test.ts` — binaire résumé, troncature, objets JSON, cycles |
|
|
440
|
+
| Intégration — trace WS | `websockets/websocket-trace-logging.test.ts` — frames RECEIVE/SEND corrélées `requestId`, cap, binaire |
|
|
441
|
+
| Intégration — santé | `http/health.test.ts` — sondes hors pipeline (pas de log par sonde, pas de `Set-Cookie`) |
|
|
442
|
+
|
|
443
|
+
Ce qui **manque** aujourd'hui : pas de banc unitaire isolé pour `trace.ts` (`resolveTraceparent`/
|
|
444
|
+
`parseTraceparent` sont exercés indirectement via les tests HTTP `traceparent` et `httpKernel`), et pas de
|
|
445
|
+
test dédié à la bascule `applyRequestLoggerFromConfig` par environnement (`auto` → pretty/json) — le
|
|
446
|
+
comportement est couvert transitivement par les tests de format.
|
|
447
|
+
|
|
448
|
+
Suites : `npm test` (unitaires), `npm run test:integration` (serveur requis). Couverture :
|
|
449
|
+
`npm run coverage` dans `@nodefony/http` — le pourcentage vit dans le rapport vitest, jamais figé ici.
|
|
450
|
+
Skills associés : `nodefony-check-memory-health`, `nodefony-load-test`, `nodefony-security-review`.
|
|
451
|
+
|
|
452
|
+
## 🔗 Pour aller plus loin
|
|
453
|
+
|
|
454
|
+
- ⬆️ **Retour au hub** : [@nodefony/http — vue du module](index.md) · [Toute la documentation](../../../../../docs/index.md)
|
|
455
|
+
- 🧭 **Pages sœurs** : [Serveurs](servers.md) · [Sessions](session.md)
|
|
456
|
+
- Où partent les logs (stdout, fichier, Loki, OpenSearch) + format RFC 5424 → [Journalisation (Syslog)](../../../../nodefony/docs/syslog.md)
|
|
457
|
+
- Le trajet complet d'une requête (où s'insèrent corrélation et trace) → [pipeline-requete](../../../../../docs/architecture/pipeline-requete.md)
|
|
458
|
+
- Sondes de santé et arrêt gracieux → [Serveurs](servers.md)
|
|
459
|
+
- Configuration d'application (`defineConfig`, `use`, `log`) → [configuration](../../../../../docs/guides/configuration.md)
|
|
460
|
+
- Zones, authentification et audit applicatif par-dessus → [Firewall](../../security/docs/firewall.md)
|