@nodefony/http 10.0.0-beta.4 → 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/index.js +3 -2
- package/dist/nodefony/config/config.js +2 -1
- package/dist/nodefony/interfaces/IRequestTrace.js +1 -0
- package/dist/nodefony/service/ProfilerAdminApi.js +12 -1
- package/dist/nodefony/service/audit-logger.js +3 -17
- package/dist/nodefony/service/http-kernel.js +80 -7
- package/dist/nodefony/service/pretty-request-logger.js +11 -10
- package/dist/nodefony/src/context/Context.js +56 -13
- package/dist/nodefony/src/context/http/HttpContext.js +21 -0
- package/dist/nodefony/src/context/http2/Response.js +20 -1
- package/dist/nodefony/src/context/requestTrace.js +104 -0
- package/dist/nodefony/src/context/timingMode.js +21 -0
- package/dist/nodefony/src/context/unsentResponse.js +30 -0
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +12 -8
- package/dist/nodefony/src/profiler/FrameProfile.js +22 -0
- package/dist/nodefony/src/profiler/Profiler.js +11 -21
- package/dist/nodefony/src/profiler/aggregate.js +26 -6
- package/dist/types/index.d.ts +2 -0
- package/dist/types/nodefony/config/config.d.ts +4 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +9 -0
- 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 +16 -0
- package/dist/types/nodefony/interfaces/index.d.ts +1 -0
- 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/http-kernel.d.ts +50 -0
- package/dist/types/nodefony/src/context/Context.d.ts +25 -1
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +15 -1
- package/dist/types/nodefony/src/context/http2/Response.d.ts +12 -0
- 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/unsentResponse.d.ts +16 -0
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +1 -1
- package/dist/types/nodefony/src/profiler/FrameProfile.d.ts +15 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +16 -101
- package/dist/types/nodefony/src/profiler/aggregate.d.ts +23 -106
- package/docs/diagnostics-channel.md +303 -0
- package/docs/index.md +6 -1
- package/docs/observabilite.md +94 -16
- package/docs/reverse-proxy.md +18 -16
- package/package.json +3 -3
|
@@ -134,6 +134,8 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
|
|
|
134
134
|
private errorRenderer;
|
|
135
135
|
private requestLogger;
|
|
136
136
|
private profiler;
|
|
137
|
+
/** `timing.tooling` : profiler aussi l'outillage (lu une fois, à onReady). */
|
|
138
|
+
private profileTooling;
|
|
137
139
|
private _trustProxyChecker;
|
|
138
140
|
private _wsOriginPolicy;
|
|
139
141
|
private rateLimiter;
|
|
@@ -186,6 +188,32 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
|
|
|
186
188
|
* tel quel plutôt que d'inventer un état.
|
|
187
189
|
*/
|
|
188
190
|
get rateLimitStore(): IRateLimitStore | null;
|
|
191
|
+
/**
|
|
192
|
+
* Cette requête — ou cette frame du pont — se profile-t-elle ? Jamais sans
|
|
193
|
+
* profileur (production). L'outillage (console, profileur, journaux, Vite :
|
|
194
|
+
* `isProfiledUrl`, sur la règle `isConsolePath` du cœur) seulement sous
|
|
195
|
+
* `timing.tooling` — on ne
|
|
196
|
+
* mesure pas la façon dont on mesure : ni tampon SQL, ni instantané, ni place
|
|
197
|
+
* dans les anneaux du profileur.
|
|
198
|
+
*
|
|
199
|
+
* @param url - URL ou chemin de la requête
|
|
200
|
+
* @returns `true` quand le profil doit être pris
|
|
201
|
+
*/
|
|
202
|
+
shouldProfile(url: string): boolean;
|
|
203
|
+
/**
|
|
204
|
+
* En développement, signale une action qui a rendu une valeur que le
|
|
205
|
+
* Resolver ne sait pas envoyer (`null`, `undefined`, instance de classe)
|
|
206
|
+
* et n'a rien envoyé elle-même : la requête attend son délai d'expiration.
|
|
207
|
+
* Le message nomme l'action, ce qu'elle a rendu et le geste qui convient
|
|
208
|
+
* (`unsentResponseWarning`).
|
|
209
|
+
*
|
|
210
|
+
* Appelé au timeout, AVANT que le 504/408 parte — après, `sended` est vrai et
|
|
211
|
+
* plus rien ne se signalerait — et au démontage, pour un client parti avant.
|
|
212
|
+
* Gratuit en production : rien n'est lu au-delà de `waitAsync`.
|
|
213
|
+
*
|
|
214
|
+
* @param context - le contexte HTTP de la requête
|
|
215
|
+
*/
|
|
216
|
+
warnUnsentResponse(context: HttpContext): void;
|
|
189
217
|
private configureRateLimit;
|
|
190
218
|
/**
|
|
191
219
|
* (Re)configure le backstop OPT-IN de connexions WS concurrentes par IP (F6c)
|
|
@@ -474,6 +502,18 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
|
|
|
474
502
|
* @returns le contexte servi, ou sa promesse.
|
|
475
503
|
*/
|
|
476
504
|
handleHttp(scope: Scope, request: httpRequest, response: httpResponse, type: ServerType): MaybePromise<HttpContext>;
|
|
505
|
+
/**
|
|
506
|
+
* Sert un contexte HTTP fraîchement créé : le crochet `onCreateContext`
|
|
507
|
+
* s'il a des écouteurs, puis le pipeline.
|
|
508
|
+
*
|
|
509
|
+
* Hot path : `fireAsync` est une fonction async → 1 Promise + 1 microtask
|
|
510
|
+
* MÊME à 0 listener. En prod sans @nodefony/security ces seams
|
|
511
|
+
* (onCreateContext/beforeResolve/afterAuth) n'ont aucun listener → garde
|
|
512
|
+
* `listenerCount` (O(1), 0 alloc) pour ne RIEN planifier.
|
|
513
|
+
*
|
|
514
|
+
* @returns le contexte servi, ou sa promesse
|
|
515
|
+
*/
|
|
516
|
+
private dispatchHttpContext;
|
|
477
517
|
/**
|
|
478
518
|
* Traceparent, tampon du profileur, puis le pipeline dans la bulle ALS de la
|
|
479
519
|
* requête (`RequestContext.run`).
|
|
@@ -578,6 +618,16 @@ declare class HttpKernel extends Service implements IHttpKernelInterface {
|
|
|
578
618
|
* @returns ce que rend l'action de handshake
|
|
579
619
|
*/
|
|
580
620
|
handleWebsocket(scope: Scope, req: IncomingMessage, admission: IUpgradeAdmission, type: ServerType): Promise<unknown>;
|
|
621
|
+
/**
|
|
622
|
+
* Fin du canal `nodefony.http.request` pour un handshake interrompu par une
|
|
623
|
+
* exception — sans effet si un refus l'a déjà publiée (`refuseHandshake`).
|
|
624
|
+
* Le statut est un fait, pas une déduction : une socket rattachée dit que
|
|
625
|
+
* le `101` est parti ; sinon c'est le `500` du filet qui a été rendu.
|
|
626
|
+
*
|
|
627
|
+
* @param context - le contexte du handshake, `null` s'il n'a pu être construit
|
|
628
|
+
* @param error - l'exception qui a interrompu le pipeline
|
|
629
|
+
*/
|
|
630
|
+
private closeFailedWebsocketTrace;
|
|
581
631
|
private releaseOrphanWsScope;
|
|
582
632
|
/**
|
|
583
633
|
* Juge un handshake WebSocket AVANT son acceptation : hôte, origine,
|
|
@@ -97,6 +97,12 @@ declare class Context extends Service implements IContextInterface {
|
|
|
97
97
|
crossDomain: boolean;
|
|
98
98
|
router: IRequestRouter | null;
|
|
99
99
|
resolver: IRouteResolver | null;
|
|
100
|
+
/**
|
|
101
|
+
* Module qui signe les lignes de CETTE requête : la ligne d'accès et les
|
|
102
|
+
* lignes du contrôleur. `null` = le module de la route (sans route : le
|
|
103
|
+
* journal du noyau). Un contrôleur le change : `this.context.logModule = "billing"`.
|
|
104
|
+
*/
|
|
105
|
+
logModule: string | null;
|
|
100
106
|
/**
|
|
101
107
|
* Intent de session de la route courante (posé par le Resolver depuis
|
|
102
108
|
* `@UseSession` / paramètre `@Session`). Pilote le point d'activation unique
|
|
@@ -128,6 +134,9 @@ declare class Context extends Service implements IContextInterface {
|
|
|
128
134
|
securityTrace: ISecurityTrace | null;
|
|
129
135
|
private readonly _timingEnabled;
|
|
130
136
|
private readonly _timingVerbose;
|
|
137
|
+
private readonly _clockEnabled;
|
|
138
|
+
private readonly _clockStartMs;
|
|
139
|
+
private _durationMs;
|
|
131
140
|
readonly phases: PhaseTiming[];
|
|
132
141
|
private _phaseIndex;
|
|
133
142
|
private _afterResponseFns;
|
|
@@ -161,6 +170,21 @@ declare class Context extends Service implements IContextInterface {
|
|
|
161
170
|
get timingEnabled(): boolean;
|
|
162
171
|
phaseStart(name: PhaseName): void;
|
|
163
172
|
phaseEnd(name: PhaseName): void;
|
|
173
|
+
/**
|
|
174
|
+
* Arrête l'horloge de la requête et rend sa durée, en millisecondes.
|
|
175
|
+
*
|
|
176
|
+
* La durée court de la construction du contexte au PREMIER appel : le
|
|
177
|
+
* démontage HTTP (`close` de la réponse, dernier octet remis — la mesure
|
|
178
|
+
* d'OpenTelemetry et du `$request_time` de nginx), la ligne d'accès pour un
|
|
179
|
+
* handshake WebSocket. Les appels suivants rendent la même valeur : journal
|
|
180
|
+
* d'accès, profiler et trace verbose lisent UNE durée, jamais trois.
|
|
181
|
+
*
|
|
182
|
+
* Active par défaut dans tous les environnements, production comprise :
|
|
183
|
+
* seul `timing.enabled: false` la coupe (cf `resolveTimingMode`).
|
|
184
|
+
*
|
|
185
|
+
* @returns la durée, ou `null` si `timing.enabled` vaut `false`
|
|
186
|
+
*/
|
|
187
|
+
stopClock(): number | null;
|
|
164
188
|
onAfterResponse(fn: AfterResponseHandler): void;
|
|
165
189
|
/**
|
|
166
190
|
* Exécute les hooks `onAfterResponse`, une seule fois, dans l'ordre
|
|
@@ -176,7 +200,7 @@ declare class Context extends Service implements IContextInterface {
|
|
|
176
200
|
private runAfterResponseHooks;
|
|
177
201
|
get signal(): AbortSignal;
|
|
178
202
|
_abortIfPending(reason?: string): void;
|
|
179
|
-
log(pci: Pci, severity?: Severity, msgid?: Msgid, msg?: Message): Pdu;
|
|
203
|
+
log(pci: Pci, severity?: Severity, msgid?: Msgid, msg?: Message, moduleName?: string): Pdu;
|
|
180
204
|
/**
|
|
181
205
|
* Confie au contexte le magasin `RequestContext` de son unité de travail
|
|
182
206
|
* (requête HTTP, connexion WebSocket), pour qu'il le VIDE à son nettoyage.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ServerType, SchemeType } from "../../../service/http-kernel.js";
|
|
2
|
+
import HttpError from "../../errors/httpError.js";
|
|
2
3
|
import Context, { HTTPMethod } from "../Context.js";
|
|
3
|
-
import { Container, Scope } from "nodefony";
|
|
4
|
+
import { Container, Scope, nodefonyError } from "nodefony";
|
|
4
5
|
import type { MaybePromise } from "nodefony";
|
|
5
6
|
import HttpRequest from "./Request.js";
|
|
6
7
|
import HttpResponse from "./Response.js";
|
|
@@ -50,6 +51,11 @@ declare class HttpContext extends Context implements IHttpContextInterface {
|
|
|
50
51
|
private activeSocket;
|
|
51
52
|
isRedirect: boolean;
|
|
52
53
|
sended: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Requête relayée par le proxy inverse : la politique de ligne d'accès de
|
|
56
|
+
* son montage. `null` = servie par l'application (cas de toute requête).
|
|
57
|
+
*/
|
|
58
|
+
relayAccessLog: "all" | "errors" | null;
|
|
53
59
|
request: HttpRequestType;
|
|
54
60
|
response: HttpRsponseType;
|
|
55
61
|
uploadService: uploadService | null;
|
|
@@ -84,6 +90,14 @@ declare class HttpContext extends Context implements IHttpContextInterface {
|
|
|
84
90
|
*/
|
|
85
91
|
handle(): MaybePromise<this>;
|
|
86
92
|
setTimeout(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Ligne d'accès — pour une requête RELAYÉE, avec le statut que l'amont a
|
|
95
|
+
* réellement rendu (le relais écrit la réponse brute, pas celle du
|
|
96
|
+
* contexte) ; un montage `accessLog: "errors"` ne l'écrit qu'en échec.
|
|
97
|
+
*
|
|
98
|
+
* @param httpError - l'erreur de la requête, s'il y en a une
|
|
99
|
+
*/
|
|
100
|
+
logRequest(httpError?: Error | HttpError | nodefonyError | null): import("nodefony").Pdu | undefined;
|
|
87
101
|
/**
|
|
88
102
|
* Nettoie le contexte, et le DÉTACHE de sa socket h1 : la connexion
|
|
89
103
|
* persistante survit à la requête, et l'entrée de `socketActiveContext`
|
|
@@ -51,6 +51,18 @@ declare class Http2Response extends HttpResponse {
|
|
|
51
51
|
* @throws de façon SYNCHRONE ce que lève la fin du flux.
|
|
52
52
|
*/
|
|
53
53
|
end(chunk?: string | Buffer, encoding?: BufferEncoding): MaybePromise<http.ServerResponse | http2.ServerHttp2Stream>;
|
|
54
|
+
/**
|
|
55
|
+
* Termine la réponse de compatibilité d'un HEAD, dont Node a clos le flux de
|
|
56
|
+
* lui-même au `respond()`.
|
|
57
|
+
*
|
|
58
|
+
* Sans ce geste, `Http2ServerResponse` n'émet ni `finish` ni `close` : le
|
|
59
|
+
* démontage de la requête, branché sur ce `close` (ligne d'accès, profil,
|
|
60
|
+
* hooks d'après-réponse, libération du scope DI), n'a jamais lieu. Les statuts
|
|
61
|
+
* que Node termine aussi d'office (204, 205, 304) émettent leur `close` seuls,
|
|
62
|
+
* et un flux détruit (client parti) aussi : ils ne sont pas touchés. Hors du
|
|
63
|
+
* chemin servi — appelé seulement quand le flux est déjà clos.
|
|
64
|
+
*/
|
|
65
|
+
private settleHeadResponse;
|
|
54
66
|
getStatusMessage(code?: number | string): string;
|
|
55
67
|
getStatus(): {
|
|
56
68
|
code: number;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { IRequestTrace, RequestTraceProtocol } from "../../interfaces/IRequestTrace.js";
|
|
2
|
+
import type Context from "./Context.js";
|
|
3
|
+
/**
|
|
4
|
+
* Nom du `TracingChannel` qui encadre chaque requête HTTP et chaque handshake
|
|
5
|
+
* WebSocket — forme `module.fonction` recommandée par la documentation de
|
|
6
|
+
* `node:diagnostics_channel` ; les sous-canaux sont
|
|
7
|
+
* `tracing:nodefony.http.request:start`, `:end`, `:asyncStart`, `:asyncEnd`,
|
|
8
|
+
* `:error`.
|
|
9
|
+
*/
|
|
10
|
+
export declare const REQUEST_TRACE_CHANNEL = "nodefony.http.request";
|
|
11
|
+
/**
|
|
12
|
+
* Le message tel que Nodefony le remplit au fil de la requête : la vue
|
|
13
|
+
* modifiable d'{@link IRequestTrace}, qui reste en lecture seule pour
|
|
14
|
+
* l'abonné.
|
|
15
|
+
*/
|
|
16
|
+
export type RequestTraceRecord = {
|
|
17
|
+
-readonly [K in keyof IRequestTrace]: IRequestTrace[K];
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Dit si quelqu'un écoute le canal — LA garde du chemin chaud : ~1,6 ns
|
|
21
|
+
* mesurées, aucune allocation. Lue une seule fois, au début de la requête :
|
|
22
|
+
* un abonné arrivé ensuite ne voit que les requêtes suivantes (règle des
|
|
23
|
+
* `TracingChannel`, qui interdit une fin sans début).
|
|
24
|
+
*
|
|
25
|
+
* @returns `true` si au moins un sous-canal a un abonné
|
|
26
|
+
*/
|
|
27
|
+
export declare function hasRequestTraceSubscribers(): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Crée le message de la requête et le range sur son contexte. À n'appeler
|
|
30
|
+
* que sous {@link hasRequestTraceSubscribers} : c'est la seule allocation
|
|
31
|
+
* du canal, et elle n'a lieu que si quelqu'un écoute.
|
|
32
|
+
*
|
|
33
|
+
* @param context - le contexte qui vient d'être construit
|
|
34
|
+
* @param protocol - requête HTTP ou handshake WebSocket
|
|
35
|
+
* @returns le message, de forme fixe, dont les champs de résultat valent `null`
|
|
36
|
+
*/
|
|
37
|
+
export declare function openRequestTrace(context: Context, protocol: RequestTraceProtocol): RequestTraceRecord;
|
|
38
|
+
/**
|
|
39
|
+
* Exécute la partie synchrone du traitement sous `start.runStores` puis
|
|
40
|
+
* publie `end` — la séquence de `TracingChannel.tracePromise`. Les stores
|
|
41
|
+
* liés au canal (`bindStore`, ce que fait un agent APM) restent actifs pour
|
|
42
|
+
* toute la suite asynchrone de la requête.
|
|
43
|
+
*
|
|
44
|
+
* @param trace - le message de la requête
|
|
45
|
+
* @param fn - la fonction qui sert la requête
|
|
46
|
+
* @param thisArg - le receveur de `fn`
|
|
47
|
+
* @param args - les arguments de `fn`
|
|
48
|
+
* @returns ce que rend `fn` — un contexte ou sa promesse
|
|
49
|
+
*/
|
|
50
|
+
export declare function runTraced<ThisArg, Args extends unknown[], Result>(trace: RequestTraceRecord, fn: (this: ThisArg, ...args: Args) => Result, thisArg: ThisArg, ...args: Args): Result;
|
|
51
|
+
/**
|
|
52
|
+
* Termine la trace de la requête : remplit les champs de résultat puis
|
|
53
|
+
* publie `error` (s'il y a une exception), `asyncStart` et `asyncEnd`.
|
|
54
|
+
* Idempotent — le premier appel gagne, les suivants ne font rien : un
|
|
55
|
+
* handshake WebSocket a plusieurs sorties, une seule publie la fin.
|
|
56
|
+
*
|
|
57
|
+
* La durée est celle de l'horloge unique du contexte (`stopClock`), la même
|
|
58
|
+
* que celle de la ligne d'accès : le canal n'en mesure aucune.
|
|
59
|
+
*
|
|
60
|
+
* @param context - le contexte de la requête
|
|
61
|
+
* @param statusCode - le statut rendu au client, `null` s'il n'y en a pas eu
|
|
62
|
+
* @param error - l'exception attrapée par le pipeline, `null` ou `undefined` sinon
|
|
63
|
+
*/
|
|
64
|
+
export declare function closeRequestTrace(context: Context, statusCode: number | null, error: unknown): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ce que le chronométrage mesure sur une requête :
|
|
3
|
+
*
|
|
4
|
+
* - `"off"` — rien : ni horloge, ni phases ; la ligne d'accès sort sans durée ;
|
|
5
|
+
* - `"clock"` — la durée seule : une lecture d'horloge à l'entrée, une à la fin ;
|
|
6
|
+
* - `"phases"` — la durée ET le détail par phase (resolve, firewall, action…).
|
|
7
|
+
*/
|
|
8
|
+
export type TimingMode = "off" | "clock" | "phases";
|
|
9
|
+
/**
|
|
10
|
+
* Résout le mode de chronométrage d'une requête depuis `timing.enabled` et
|
|
11
|
+
* l'environnement — la SEULE règle, appelée par le constructeur du `Context`.
|
|
12
|
+
*
|
|
13
|
+
* Défaut (`null`, pas d'avis) : la durée partout, les phases hors production.
|
|
14
|
+
* La durée coûte deux lectures d'horloge, l'équivalent de quelques pourcents
|
|
15
|
+
* de la ligne d'accès qu'elle complète ; les phases coûtent une lecture par
|
|
16
|
+
* phase, un tableau et un index par requête — réservées au développement, ou
|
|
17
|
+
* à un diagnostic à chaud (`true`). `false` coupe tout, durée comprise.
|
|
18
|
+
*
|
|
19
|
+
* @param enabled - valeur de `timing.enabled` (`true`, `false`, `null`)
|
|
20
|
+
* @param environment - environnement normalisé du noyau (`"production"`…)
|
|
21
|
+
* @returns le mode de la requête — une chaîne littérale, rien d'alloué
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveTimingMode(enabled: unknown, environment: string | undefined): TimingMode;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IRouteResolver } from "../../interfaces/IRouting.js";
|
|
2
|
+
/**
|
|
3
|
+
* Rédige l'avertissement de développement d'une requête restée sans réponse :
|
|
4
|
+
* l'action a rendu une valeur que le framework ne sait pas envoyer, et la
|
|
5
|
+
* requête a attendu son délai d'expiration.
|
|
6
|
+
*
|
|
7
|
+
* Le message nomme l'action fautive, ce qu'elle a rendu, et le geste qui
|
|
8
|
+
* convient à ce cas — `null` est presque toujours une entité absente, qui
|
|
9
|
+
* appelle un 404.
|
|
10
|
+
*
|
|
11
|
+
* @param resolver - le resolver de la requête (route, controller, action,
|
|
12
|
+
* valeur non rendue), `null` si aucune route n'a été résolue.
|
|
13
|
+
* @param url - l'URL de la requête, repli quand aucune route n'est connue.
|
|
14
|
+
* @returns le texte de l'avertissement.
|
|
15
|
+
*/
|
|
16
|
+
export declare function unsentResponseWarning(resolver: IRouteResolver | null | undefined, url: string): string;
|
|
@@ -87,7 +87,7 @@ declare class WebsocketContext extends Context implements IWebsocketContextInter
|
|
|
87
87
|
query: Record<string, string>;
|
|
88
88
|
wsPath: string;
|
|
89
89
|
constructor(scope: Scope, req: IncomingMessage, ws: Ws | null, serverType: ServerType);
|
|
90
|
-
log(pci: unknown, severity?: Severity, msgid?: Msgid, msg?: Message): import("nodefony").Pdu;
|
|
90
|
+
log(pci: unknown, severity?: Severity, msgid?: Msgid, msg?: Message, moduleName?: string): import("nodefony").Pdu;
|
|
91
91
|
logRequest(httpError?: Error | HttpError | nodefonyError | null, acceptedProtocol?: string | null): import("nodefony").Pdu | undefined;
|
|
92
92
|
/**
|
|
93
93
|
* Rattache la socket que le handshake vient d'ouvrir (`101` envoyé) : le
|
|
@@ -80,6 +80,12 @@ export declare class FrameProfile {
|
|
|
80
80
|
* invocation, il redevient exact, et le SQL se replace dans le waterfall.
|
|
81
81
|
*/
|
|
82
82
|
readonly profilerQueries: IProfilerQuery[] | null;
|
|
83
|
+
/**
|
|
84
|
+
* Ce profil sera-t-il gardé par le Profiler ? Faux quand seul le timing
|
|
85
|
+
* l'a ouvert (chemin d'outillage) : son id ne désigne alors aucun profil
|
|
86
|
+
* lisible, et ne doit pas être annoncé au client.
|
|
87
|
+
*/
|
|
88
|
+
get profiled(): boolean;
|
|
83
89
|
/** Resolver de la frame — porte route / controller / action. */
|
|
84
90
|
resolver: ProfiledResolver | null;
|
|
85
91
|
/** Statut HTTP-équivalent de l'invocation (200, 403, 404…). */
|
|
@@ -90,7 +96,16 @@ export declare class FrameProfile {
|
|
|
90
96
|
message: string;
|
|
91
97
|
} | null;
|
|
92
98
|
private readonly timing;
|
|
99
|
+
private readonly clockStartMs;
|
|
100
|
+
private durationMs;
|
|
93
101
|
constructor(init: FrameProfileInit);
|
|
102
|
+
/**
|
|
103
|
+
* Arrête l'horloge de la frame (au premier appel) et rend sa durée en ms ;
|
|
104
|
+
* `null` si le timing est éteint. Contrat de `IContext.stopClock`.
|
|
105
|
+
*
|
|
106
|
+
* @returns la durée de l'invocation, ou `null`
|
|
107
|
+
*/
|
|
108
|
+
stopClock(): number | null;
|
|
94
109
|
/**
|
|
95
110
|
* Ouvre une phase. Contrairement au `Context`, l'index est un **scan arrière**
|
|
96
111
|
* (pas de `Map<nom, index>`) : une frame porte une poignée de phases, et une
|
|
@@ -20,106 +20,12 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import type { ProfiledResolver } from "./FrameProfile.js";
|
|
22
22
|
import type { ProtocolName } from "../../interfaces/IContext.js";
|
|
23
|
-
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
durationMs: number | null;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Une requête SQL/NoSQL exécutée pendant la requête HTTP (SEAM ORM — futur).
|
|
33
|
-
*
|
|
34
|
-
* Non collecté aujourd'hui : quand `@nodefony/orm-core` arrivera, les adapters
|
|
35
|
-
* pousseront leurs requêtes dans un buffer per-request dev-only (via l'ALS
|
|
36
|
-
* `RequestContext.getRequestId()`, déjà en place) que `collect()` lira ici.
|
|
37
|
-
* Le champ `queries` reste donc `undefined` tant qu'aucun ORM ne pushe → 0 coût.
|
|
38
|
-
*/
|
|
39
|
-
export interface ProfileQuery {
|
|
40
|
-
/** Requête (SQL ou commande NoSQL), tronquée si volumineuse. */
|
|
41
|
-
sql: string;
|
|
42
|
-
/** Début relatif (`performance.now()`) — même horloge que les phases. */
|
|
43
|
-
startMs?: number | undefined;
|
|
44
|
-
/** Durée d'exécution en ms. */
|
|
45
|
-
durationMs: number;
|
|
46
|
-
/** Lignes affectées/retournées, si connu. */
|
|
47
|
-
rows?: number | undefined;
|
|
48
|
-
/** Connecteur émetteur (`drizzle`, `mongoose`…). */
|
|
49
|
-
connector?: string | undefined;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Traversée du firewall par cette requête — **ce qui était possible** (la zone :
|
|
53
|
-
* son nom, si elle est protégée, quels authenticators elle accepte) croisé avec
|
|
54
|
-
* **ce qui s'est passé** (quel maillon a résolu l'identité, l'issue, le motif
|
|
55
|
-
* d'un refus).
|
|
56
|
-
*
|
|
57
|
-
* Sans cela, une requête qui PASSE est invisible côté sécurité : le chemin de
|
|
58
|
-
* succès n'émet aucun événement d'audit (choix délibéré — le volume nominal
|
|
59
|
-
* n'est pas un signal). `undefined` hors zone firewall.
|
|
60
|
-
*/
|
|
61
|
-
export interface ProfileSecurity {
|
|
62
|
-
/** Nom de la zone traversée. */
|
|
63
|
-
zone: string | null;
|
|
64
|
-
/** La zone exige-t-elle une identité (`security: true`) ? */
|
|
65
|
-
protected: boolean;
|
|
66
|
-
/** Chaîne d'authenticators : `first` = le premier qui supporte, `all` = MFA. */
|
|
67
|
-
mode: string | null;
|
|
68
|
-
/** Authenticators que la zone accepte (ce qui était POSSIBLE). */
|
|
69
|
-
candidates: string[];
|
|
70
|
-
/** Authenticator qui a RÉELLEMENT résolu l'identité. */
|
|
71
|
-
authenticator: string | null;
|
|
72
|
-
/** `granted` · `anonymous` · `denied` · `failure` · `throttled` · `bypass` · `public`. */
|
|
73
|
-
outcome: string | null;
|
|
74
|
-
/** Motif du refus (`no_credentials`, `invalid_credentials`…). */
|
|
75
|
-
reason: string | null;
|
|
76
|
-
/** Rôles du token résolu (l'axe autorisation). */
|
|
77
|
-
roles: string[] | null;
|
|
78
|
-
}
|
|
79
|
-
/** Profil complet d'une requête (HTTP ou message WS), exposé par `get`. */
|
|
80
|
-
export interface ProfileEntry {
|
|
81
|
-
requestId: string;
|
|
82
|
-
/**
|
|
83
|
-
* Numéro d'ordre de collecte, croissant sur la vie du profileur. Un profil
|
|
84
|
-
* réinséré (identifiant réutilisé) reçoit un numéro neuf.
|
|
85
|
-
*/
|
|
86
|
-
seq: number;
|
|
87
|
-
/** Horodatage de la collecte (`Date.now()`), pour le tri client. */
|
|
88
|
-
ts: number;
|
|
89
|
-
kind: "http" | "ws";
|
|
90
|
-
method: string | null;
|
|
91
|
-
url: string;
|
|
92
|
-
scheme: string;
|
|
93
|
-
status: number | null;
|
|
94
|
-
/** Durée totale serveur (ms), dérivée des phases. */
|
|
95
|
-
durationMs: number | null;
|
|
96
|
-
route: string | null;
|
|
97
|
-
controller: string | null;
|
|
98
|
-
action: string | null;
|
|
99
|
-
remoteAddress: string | null;
|
|
100
|
-
/** Identité (username) si le firewall l'a injectée, sinon `null`. */
|
|
101
|
-
user: string | null;
|
|
102
|
-
/** `traceparent` W3C Trace Context (P2.7) — corrélation distribuée RFC-propre. */
|
|
103
|
-
traceparent: string | null;
|
|
104
|
-
error: string | null;
|
|
105
|
-
phases: ProfilePhase[];
|
|
106
|
-
/** Requêtes ORM (SEAM futur — `undefined` tant qu'aucun adapter ne pushe). */
|
|
107
|
-
queries?: ProfileQuery[] | undefined;
|
|
108
|
-
/** Traversée du firewall — `undefined` si la requête n'a croisé aucune zone. */
|
|
109
|
-
security?: ProfileSecurity | undefined;
|
|
110
|
-
}
|
|
111
|
-
/** Résumé léger d'un profil pour la liste `recent` (sans les phases). */
|
|
112
|
-
export interface ProfileSummary {
|
|
113
|
-
requestId: string;
|
|
114
|
-
ts: number;
|
|
115
|
-
kind: "http" | "ws";
|
|
116
|
-
method: string | null;
|
|
117
|
-
url: string;
|
|
118
|
-
status: number | null;
|
|
119
|
-
durationMs: number | null;
|
|
120
|
-
route: string | null;
|
|
121
|
-
error: string | null;
|
|
122
|
-
}
|
|
23
|
+
import type { IProfileEntry, IProfilePhase, IProfileQuery, IProfileSecurity, IProfileSummary } from "nodefony";
|
|
24
|
+
export type ProfilePhase = IProfilePhase;
|
|
25
|
+
export type ProfileQuery = IProfileQuery;
|
|
26
|
+
export type ProfileSecurity = IProfileSecurity;
|
|
27
|
+
export type ProfileEntry = IProfileEntry;
|
|
28
|
+
export type ProfileSummary = IProfileSummary;
|
|
123
29
|
/** Forme structurelle minimale lue sur un `Context` (lecture défensive). */
|
|
124
30
|
interface ProfilableContext {
|
|
125
31
|
requestId?: string | undefined;
|
|
@@ -142,6 +48,11 @@ interface ProfilableContext {
|
|
|
142
48
|
durationMs?: number | undefined;
|
|
143
49
|
}> | undefined;
|
|
144
50
|
resolver?: ProfiledResolver | null | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Arrête l'horloge de la requête et rend sa durée (ms) — la même que le
|
|
53
|
+
* journal d'accès (`IContext.stopClock`). `null` si `timing.enabled` vaut `false`.
|
|
54
|
+
*/
|
|
55
|
+
stopClock(): number | null;
|
|
145
56
|
/**
|
|
146
57
|
* Buffer de requêtes ORM rempli pendant la requête (dev-only). Même tableau
|
|
147
58
|
* que la payload ALS — les adapters ORM y poussent via `RequestContext`.
|
|
@@ -222,8 +133,12 @@ export declare class Profiler implements IProfilerReader {
|
|
|
222
133
|
get(requestId: string): ProfileEntry | undefined;
|
|
223
134
|
/**
|
|
224
135
|
* Résumés des requêtes les plus récentes (récent → ancien), capés à `limit`.
|
|
136
|
+
*
|
|
137
|
+
* @param limit - nombre maximal de résumés
|
|
138
|
+
* @param connection - le requestId d'une connexion WebSocket : ses SEULES
|
|
139
|
+
* frames du pont (`<connection>.<n>`, règle `frameIndexOf` du cœur)
|
|
225
140
|
*/
|
|
226
|
-
recent(limit?: number): ProfileSummary[];
|
|
141
|
+
recent(limit?: number, connection?: string): ProfileSummary[];
|
|
227
142
|
/** Dernier numéro d'ordre attribué : le point de départ d'une lecture suivie. */
|
|
228
143
|
get cursor(): number;
|
|
229
144
|
/**
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ProfileEntry } from "./Profiler.js";
|
|
2
|
+
import type { IStatusBreakdown, IPhaseTotal, IRouteStats, INPlusOne, ISqlShapeStats, IErrorSample, IProfileReport } from "nodefony";
|
|
3
|
+
export type { IStatusBreakdown, IPhaseTotal, IRouteStats, INPlusOne, ISqlShapeStats, IErrorSample, IProfileReport, };
|
|
2
4
|
/**
|
|
3
5
|
* Agrégation des profils de requêtes — règle PURE, sans horloge ni I/O.
|
|
4
6
|
*
|
|
@@ -21,7 +23,14 @@ export declare const ROUTE_SAMPLE_CAP = 256;
|
|
|
21
23
|
export declare const SQL_SHAPE_CAP = 200;
|
|
22
24
|
/** Exemples d'erreurs retenus (statut ≥ 500 ou exception). */
|
|
23
25
|
export declare const ERROR_SAMPLE_CAP = 10;
|
|
24
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Préfixe de l'espace réservé au framework.
|
|
28
|
+
*
|
|
29
|
+
* @deprecated Ne dit pas ce qui est de l'outillage : une connexion
|
|
30
|
+
* d'utilisateur (`/nodefony/security/api/auth/…`) ou une route d'application
|
|
31
|
+
* (`/nodefony/test/index`) passent sous ce préfixe. La règle est
|
|
32
|
+
* `isConsolePath` (`nodefony`), que `profileLane` appelle.
|
|
33
|
+
*/
|
|
25
34
|
export declare const TOOLING_URL_PREFIX = "/nodefony/";
|
|
26
35
|
/**
|
|
27
36
|
* Provenance d'un profil : l'application, l'outillage (`/nodefony/…` :
|
|
@@ -30,8 +39,10 @@ export declare const TOOLING_URL_PREFIX = "/nodefony/";
|
|
|
30
39
|
*/
|
|
31
40
|
export type ProfileLane = "app" | "tooling" | "asset";
|
|
32
41
|
/**
|
|
33
|
-
* Range un profil par provenance, d'après son URL.
|
|
34
|
-
*
|
|
42
|
+
* Range un profil par provenance, d'après son URL. L'outillage est reconnu par
|
|
43
|
+
* la règle du cœur, `isConsolePath` — la même que les Journaux, la barre de
|
|
44
|
+
* débogage et le Studio : une connexion d'utilisateur reste de l'application.
|
|
45
|
+
* Les anneaux du profileur et le filtre de l'agrégation la lisent tous deux.
|
|
35
46
|
*
|
|
36
47
|
* @param url - URL du profil, absolue ou chemin.
|
|
37
48
|
* @returns la provenance du profil.
|
|
@@ -44,113 +55,19 @@ export declare function profileLane(url: string): ProfileLane;
|
|
|
44
55
|
* @param url - URL du profil, absolue ou chemin.
|
|
45
56
|
*/
|
|
46
57
|
export declare function isToolingUrl(url: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Une requête de cette URL se profile-t-elle ? L'application toujours ;
|
|
60
|
+
* l'outillage et les assets relayés seulement sur demande (`timing.tooling`)
|
|
61
|
+
* — on ne mesure pas la façon dont on mesure.
|
|
62
|
+
*
|
|
63
|
+
* @param url - URL ou chemin de la requête (ou de la frame du pont)
|
|
64
|
+
* @param tooling - profiler aussi l'outillage
|
|
65
|
+
*/
|
|
66
|
+
export declare function isProfiledUrl(url: string, tooling: boolean): boolean;
|
|
47
67
|
/** Groupe des requêtes qu'aucune route n'a servies (404, statique, sondes). */
|
|
48
68
|
export declare const NO_ROUTE = "(aucune route)";
|
|
49
69
|
/** Pseudo-phase : le temps de l'action hors phases imbriquées et hors SQL. */
|
|
50
70
|
export declare const ACTION_SELF_PHASE = "action.self";
|
|
51
|
-
/** Répartition des statuts HTTP d'une route. */
|
|
52
|
-
export interface IStatusBreakdown {
|
|
53
|
-
s2xx: number;
|
|
54
|
-
s3xx: number;
|
|
55
|
-
s4xx: number;
|
|
56
|
-
s5xx: number;
|
|
57
|
-
/** Dont 401 : la route exige une authentification que la requête n'avait pas. */
|
|
58
|
-
unauthorized: number;
|
|
59
|
-
/** Dont 403 : authentifiée, mais refusée. */
|
|
60
|
-
forbidden: number;
|
|
61
|
-
}
|
|
62
|
-
/** Temps cumulé d'une phase sur les requêtes d'une route. */
|
|
63
|
-
export interface IPhaseTotal {
|
|
64
|
-
totalMs: number;
|
|
65
|
-
/** Requêtes où la phase est apparue et s'est terminée. */
|
|
66
|
-
count: number;
|
|
67
|
-
}
|
|
68
|
-
/** Statistiques d'une route sur la fenêtre agrégée. */
|
|
69
|
-
export interface IRouteStats {
|
|
70
|
-
route: string;
|
|
71
|
-
kind: "http" | "ws";
|
|
72
|
-
controller: string | null;
|
|
73
|
-
action: string | null;
|
|
74
|
-
count: number;
|
|
75
|
-
/** Requêtes sans durée mesurée (mesure des phases désactivée, erreur précoce). */
|
|
76
|
-
untimed: number;
|
|
77
|
-
/**
|
|
78
|
-
* Requêtes SERVIES et chronométrées (statut < 400, sans exception) : la
|
|
79
|
-
* population de tout ce qui se dit « par requête » ci-dessous. Refus et
|
|
80
|
-
* erreurs se lisent dans `status` et `errors`.
|
|
81
|
-
*/
|
|
82
|
-
served: number;
|
|
83
|
-
/** Percentiles et maximum, sur les requêtes servies. */
|
|
84
|
-
p50Ms: number | null;
|
|
85
|
-
p95Ms: number | null;
|
|
86
|
-
maxMs: number | null;
|
|
87
|
-
status: IStatusBreakdown;
|
|
88
|
-
/** Requêtes en erreur : statut ≥ 500 ou exception levée. */
|
|
89
|
-
errors: number;
|
|
90
|
-
/**
|
|
91
|
-
* Requêtes SQL/NoSQL des requêtes servies : total, maximum dans une
|
|
92
|
-
* requête, temps CUMULÉ (des requêtes parallèles s'y additionnent).
|
|
93
|
-
*/
|
|
94
|
-
queries: {
|
|
95
|
-
total: number;
|
|
96
|
-
perRequestMax: number;
|
|
97
|
-
totalMs: number;
|
|
98
|
-
};
|
|
99
|
-
/** Temps par phase des requêtes servies, `action.self` compris. */
|
|
100
|
-
phases: Record<string, IPhaseTotal>;
|
|
101
|
-
}
|
|
102
|
-
/** Un N+1 : la même forme SQL répétée dans une requête. */
|
|
103
|
-
export interface INPlusOne {
|
|
104
|
-
route: string;
|
|
105
|
-
controller: string | null;
|
|
106
|
-
action: string | null;
|
|
107
|
-
shape: string;
|
|
108
|
-
/** Plus grand nombre de répétitions vu dans une requête. */
|
|
109
|
-
maxRepeats: number;
|
|
110
|
-
/** Requêtes touchées. */
|
|
111
|
-
requests: number;
|
|
112
|
-
/** Temps cumulé de ces répétitions. */
|
|
113
|
-
totalMs: number;
|
|
114
|
-
/** Une requête touchée, pour aller voir son profil complet. */
|
|
115
|
-
sampleRequestId: string;
|
|
116
|
-
}
|
|
117
|
-
/** Une forme SQL et son coût sur la fenêtre. */
|
|
118
|
-
export interface ISqlShapeStats {
|
|
119
|
-
shape: string;
|
|
120
|
-
connector: string | null;
|
|
121
|
-
count: number;
|
|
122
|
-
totalMs: number;
|
|
123
|
-
maxMs: number;
|
|
124
|
-
/** Routes qui l'ont émise (au plus cinq). */
|
|
125
|
-
routes: string[];
|
|
126
|
-
}
|
|
127
|
-
/** Un exemple d'erreur, de quoi la retrouver dans le journal. */
|
|
128
|
-
export interface IErrorSample {
|
|
129
|
-
route: string;
|
|
130
|
-
status: number | null;
|
|
131
|
-
error: string | null;
|
|
132
|
-
requestId: string;
|
|
133
|
-
}
|
|
134
|
-
/** Le rapport agrégé. */
|
|
135
|
-
export interface IProfileReport {
|
|
136
|
-
/** Requêtes agrégées (hors outillage filtré). */
|
|
137
|
-
requests: number;
|
|
138
|
-
http: number;
|
|
139
|
-
ws: number;
|
|
140
|
-
/** Requêtes de l'outillage écartées (`/nodefony/…`, `/_vite/…`). */
|
|
141
|
-
filteredTooling: number;
|
|
142
|
-
/** Requêtes sans durée mesurée. */
|
|
143
|
-
untimed: number;
|
|
144
|
-
/** Triées par p95 décroissant. */
|
|
145
|
-
routes: IRouteStats[];
|
|
146
|
-
/** Triés par temps cumulé décroissant. */
|
|
147
|
-
nPlusOne: INPlusOne[];
|
|
148
|
-
/** Les dix formes SQL les plus coûteuses (temps cumulé décroissant). */
|
|
149
|
-
slowQueries: ISqlShapeStats[];
|
|
150
|
-
/** Formes SQL distinctes vues au-delà de la capacité, comptées sans détail. */
|
|
151
|
-
sqlShapesDropped: number;
|
|
152
|
-
errors: IErrorSample[];
|
|
153
|
-
}
|
|
154
71
|
/** Réglages d'une agrégation. */
|
|
155
72
|
export interface IProfileAggregatorOptions {
|
|
156
73
|
/** Répétitions à partir desquelles une forme SQL est un N+1. */
|