opticore-profiler 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/dist/assets/css/debug-message.css +16 -0
- package/dist/assets/css/home-page.css +177 -0
- package/dist/assets/css/profiler-detail.css +215 -0
- package/dist/assets/css/profiler-list.css +131 -0
- package/dist/assets/css/toolbar-standalone.css +213 -0
- package/dist/assets/js/home-particles.js +65 -0
- package/dist/assets/js/profiler-detail.js +77 -0
- package/dist/assets/js/profiler-list.js +76 -0
- package/dist/assets/js/toolbar.js +230 -0
- package/dist/index.cjs +1942 -0
- package/dist/index.d.cts +488 -0
- package/dist/index.d.ts +488 -0
- package/dist/index.js +1893 -0
- package/dist/utils/translations/message.translation.en.json +190 -0
- package/dist/utils/translations/message.translation.fr.json +190 -0
- package/dist/views/templates/home.njk +120 -0
- package/dist/views/templates/macros/icons.njk +19 -0
- package/dist/views/templates/macros/tables.njk +17 -0
- package/dist/views/templates/message.njk +14 -0
- package/dist/views/templates/panels/configuration.njk +16 -0
- package/dist/views/templates/panels/database.njk +36 -0
- package/dist/views/templates/panels/exception.njk +26 -0
- package/dist/views/templates/panels/logs.njk +44 -0
- package/dist/views/templates/panels/performance.njk +56 -0
- package/dist/views/templates/panels/request.njk +141 -0
- package/dist/views/templates/panels/routes.njk +51 -0
- package/dist/views/templates/panels/routing.njk +22 -0
- package/dist/views/templates/profiler-detail.njk +114 -0
- package/dist/views/templates/profiler-list.njk +163 -0
- package/dist/views/templates/toolbar-wdt.njk +161 -0
- package/package.json +73 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { Request, Response, RequestHandler, NextFunction, Application } from 'opticore-express';
|
|
3
|
+
import { TFeatureRoutes } from 'opticore-router';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A DataCollector observes one facet of a request/response cycle. Collectors
|
|
7
|
+
* never see or alter business code: they are wired up once, at bootstrap, via
|
|
8
|
+
* decoration (DB client, logger) or via the profiler middleware's own hooks
|
|
9
|
+
* (res.on('finish'), Express error middleware). `collect()` is called exactly
|
|
10
|
+
* once per profiled request, after the response has been sent.
|
|
11
|
+
*/
|
|
12
|
+
interface CollectorContext {
|
|
13
|
+
token: string;
|
|
14
|
+
req: Request;
|
|
15
|
+
res: Response;
|
|
16
|
+
/** Date.now() captured when the profiler middleware started handling this request. */
|
|
17
|
+
startTime: number;
|
|
18
|
+
/** process.hrtime.bigint() captured at the same instant, for sub-ms precision. */
|
|
19
|
+
startHrTime: bigint;
|
|
20
|
+
/** process.memoryUsage().heapUsed captured at the same instant. */
|
|
21
|
+
heapUsedBefore: number;
|
|
22
|
+
/** The response body exactly as sent to the client, captured by the profiler middleware's res.end() interception (installHtmlInjection) — undefined when nothing was ever written. */
|
|
23
|
+
responseBody?: {
|
|
24
|
+
buffer: Buffer;
|
|
25
|
+
contentType: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
interface DataCollector<T = unknown> {
|
|
29
|
+
/** Unique, stable identifier — also the key under which data is stored in Profile.collectors. */
|
|
30
|
+
getName(): string;
|
|
31
|
+
/** Called once, after the response has finished sending. May be async (e.g. flushing buffers). */
|
|
32
|
+
collect(ctx: CollectorContext, error?: Error | null): void | Promise<void>;
|
|
33
|
+
/** Returns the JSON-serializable data gathered by this collector. */
|
|
34
|
+
getData(): T;
|
|
35
|
+
/** Restores the collector to its initial state. Registry creates one instance per request, so
|
|
36
|
+
* reset() mainly matters for collectors that are reused (e.g. tests). */
|
|
37
|
+
reset(): void;
|
|
38
|
+
}
|
|
39
|
+
type CollectorFactory = () => DataCollector;
|
|
40
|
+
interface Profile {
|
|
41
|
+
token: string;
|
|
42
|
+
ip: string;
|
|
43
|
+
method: string;
|
|
44
|
+
url: string;
|
|
45
|
+
statusCode: number;
|
|
46
|
+
/** Unix ms timestamp — when the request started. */
|
|
47
|
+
time: number;
|
|
48
|
+
/** Total request duration in ms — convenience mirror of the "time" collector's data. */
|
|
49
|
+
duration: number;
|
|
50
|
+
/** Response Content-Type — convenience mirror of the "request" collector's data, kept at the top level (like duration) so lightweight index entries (FileStorage.find(), which never load collector payloads) can still tell an HTML page navigation apart from a JSON/API response. */
|
|
51
|
+
contentType?: string;
|
|
52
|
+
/** collector name -> collector.getData() */
|
|
53
|
+
collectors: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
interface ProfilerStorage {
|
|
56
|
+
write(profile: Profile): Promise<void> | void;
|
|
57
|
+
read(token: string): Promise<Profile | undefined> | Profile | undefined;
|
|
58
|
+
/** Most recent profiles first. */
|
|
59
|
+
find(limit?: number): Promise<Profile[]> | Profile[];
|
|
60
|
+
/** Removes profiles older than `olderThanMs` (relative to Date.now()). No-op if omitted from options. */
|
|
61
|
+
purge(olderThanMs?: number): Promise<void> | void;
|
|
62
|
+
clear(): Promise<void> | void;
|
|
63
|
+
}
|
|
64
|
+
interface SecurityConfig {
|
|
65
|
+
/** Header/body/query keys that get masked before being persisted or displayed (case-insensitive). */
|
|
66
|
+
sensitiveKeys: string[];
|
|
67
|
+
maxUrlLength: number;
|
|
68
|
+
}
|
|
69
|
+
interface ProfilerOptions {
|
|
70
|
+
/** Master switch. Defaults to false — must be explicitly enabled (e.g. NODE_ENV === 'development'). */
|
|
71
|
+
enabled: boolean;
|
|
72
|
+
/** URL prefix under which the profiler UI/API is mounted. Requests under this prefix are never profiled. */
|
|
73
|
+
routePrefix: string;
|
|
74
|
+
storage: ProfilerStorage;
|
|
75
|
+
/** Collector factories to run for every profiled request. Defaults to the 6 built-in collectors. */
|
|
76
|
+
collectors?: CollectorFactory[];
|
|
77
|
+
/** Retention window in ms for automatic purge (0 disables automatic purge). */
|
|
78
|
+
retentionMs?: number;
|
|
79
|
+
security?: Partial<SecurityConfig>;
|
|
80
|
+
/** Max number of AJAX/main requests remembered in the toolbar's HTTP list. */
|
|
81
|
+
maxRequests?: number;
|
|
82
|
+
/**
|
|
83
|
+
* Paths never profiled — no token, no toolbar injection, no entry in the
|
|
84
|
+
* list/SSE/toolbar HTTP-requests table. Exact match or a trailing "/*"
|
|
85
|
+
* prefix match (e.g. "/.well-known/*"). Defaults to the browser-noise
|
|
86
|
+
* requests every site gets for free (favicon, Chrome DevTools probe) so
|
|
87
|
+
* the HTTP-requests table only ever shows requests the app actually
|
|
88
|
+
* handles as business logic.
|
|
89
|
+
*/
|
|
90
|
+
excludePaths?: string[];
|
|
91
|
+
}
|
|
92
|
+
declare const DEFAULT_EXCLUDE_PATHS: readonly string[];
|
|
93
|
+
declare const DEFAULT_SENSITIVE_KEYS: readonly string[];
|
|
94
|
+
interface ISqlQuery {
|
|
95
|
+
sql: string;
|
|
96
|
+
duration: number;
|
|
97
|
+
timestamp: number;
|
|
98
|
+
bindings?: unknown[];
|
|
99
|
+
error?: string;
|
|
100
|
+
type?: string;
|
|
101
|
+
/** Origin call site, captured non-intrusively at the DB client decoration point. */
|
|
102
|
+
stack?: string;
|
|
103
|
+
}
|
|
104
|
+
interface ILogEntry {
|
|
105
|
+
level: "debug" | "info" | "warning" | "error" | "critical" | "deprecation";
|
|
106
|
+
message: string;
|
|
107
|
+
timestamp: number;
|
|
108
|
+
context?: Record<string, unknown>;
|
|
109
|
+
}
|
|
110
|
+
interface IRouteInfo {
|
|
111
|
+
path: string;
|
|
112
|
+
method: string;
|
|
113
|
+
handler?: string;
|
|
114
|
+
middlewares?: string[];
|
|
115
|
+
params?: Record<string, string | string[]>;
|
|
116
|
+
controller?: string;
|
|
117
|
+
}
|
|
118
|
+
interface IRequestInfo {
|
|
119
|
+
method: string;
|
|
120
|
+
url: string;
|
|
121
|
+
headers: Record<string, string | string[] | undefined>;
|
|
122
|
+
query: Record<string, unknown>;
|
|
123
|
+
body: Record<string, unknown>;
|
|
124
|
+
params: Record<string, string | string[]>;
|
|
125
|
+
ip: string;
|
|
126
|
+
cookies?: Record<string, string>;
|
|
127
|
+
protocol?: string;
|
|
128
|
+
hostname?: string;
|
|
129
|
+
/** `req.session`'s own data properties (e.g. express-session) — undefined when no session middleware is installed, distinct from an empty/absent session object so the Session tab can tell "not tracked" apart from "tracked but empty". */
|
|
130
|
+
session?: Record<string, unknown>;
|
|
131
|
+
}
|
|
132
|
+
interface IResponseInfo {
|
|
133
|
+
statusCode: number;
|
|
134
|
+
statusMessage: string;
|
|
135
|
+
headers: Record<string, string>;
|
|
136
|
+
contentType?: string;
|
|
137
|
+
/** Parsed from Set-Cookie response header(s), one entry per cookie name -> value — masked per-name like request cookies (see Masker), not blanket-redacted like the raw Set-Cookie header in `headers`. */
|
|
138
|
+
cookies?: Record<string, string>;
|
|
139
|
+
body?: unknown;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Central registry of collector factories. The profiler middleware asks the
|
|
144
|
+
* registry for a fresh set of collector *instances* at the start of every
|
|
145
|
+
* profiled request (never reusing instances across requests — that would
|
|
146
|
+
* leak data between unrelated requests running concurrently).
|
|
147
|
+
*
|
|
148
|
+
* Applications register custom collectors the same way built-ins are
|
|
149
|
+
* registered: `registry.register(() => new MyCollector())`.
|
|
150
|
+
*/
|
|
151
|
+
declare class CollectorRegistry {
|
|
152
|
+
private readonly factories;
|
|
153
|
+
register(factory: CollectorFactory): void;
|
|
154
|
+
unregister(name: string): void;
|
|
155
|
+
has(name: string): boolean;
|
|
156
|
+
names(): string[];
|
|
157
|
+
/** Builds one fresh instance per registered factory, keyed by collector name. */
|
|
158
|
+
createAll(): Map<string, DataCollector>;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
interface OpticoreProfiler extends RequestHandler {
|
|
162
|
+
registry: CollectorRegistry;
|
|
163
|
+
options: Readonly<Required<Pick<ProfilerOptions, "enabled" | "routePrefix" | "storage" | "retentionMs" | "maxRequests" | "excludePaths">> & {
|
|
164
|
+
security: SecurityConfig;
|
|
165
|
+
}>;
|
|
166
|
+
/** Purges every stored profile. */
|
|
167
|
+
clear(): Promise<void> | void;
|
|
168
|
+
/**
|
|
169
|
+
* Emits a "profile" event, with the lightweight IndexEntry-shaped
|
|
170
|
+
* summary, right after every profile is persisted — the push side of the
|
|
171
|
+
* profiler list's live updates (GET /_profiler/stream), so the list page
|
|
172
|
+
* never has to poll the server.
|
|
173
|
+
*/
|
|
174
|
+
events: EventEmitter;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Builds the profiler middleware. This is the single integration point an
|
|
178
|
+
* application needs: `app.use(opticoreProfiler({ storage }))`.
|
|
179
|
+
*
|
|
180
|
+
* Per request (when enabled and outside routePrefix):
|
|
181
|
+
* 1. generates a token and a fresh set of collector instances,
|
|
182
|
+
* 2. runs the rest of the request inside an AsyncLocalStorage context so
|
|
183
|
+
* instrumented code (DB driver, logger) can reach those collectors,
|
|
184
|
+
* 3. on res.on('finish'), asks every collector for its data, assembles a
|
|
185
|
+
* Profile and hands it to `storage.write()` — after the response has
|
|
186
|
+
* already been sent, so this adds no perceived latency,
|
|
187
|
+
* 4. optionally rewrites an HTML response to inject the toolbar loader
|
|
188
|
+
* snippet before `</body>`.
|
|
189
|
+
*/
|
|
190
|
+
declare function opticoreProfiler(options: Partial<ProfilerOptions> & {
|
|
191
|
+
storage: ProfilerOptions["storage"];
|
|
192
|
+
}): OpticoreProfiler;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Express error-handling middleware (4-arg signature) that records the error
|
|
196
|
+
* into the current request's ExceptionCollector, then forwards it unchanged
|
|
197
|
+
* via next(err) — it never sends a response itself, so the application's own
|
|
198
|
+
* error handling (JSON error responses, custom error pages, ...) behaves
|
|
199
|
+
* exactly as if the profiler weren't there. Mount it after all routes, same
|
|
200
|
+
* as any other Express error middleware:
|
|
201
|
+
*
|
|
202
|
+
* app.use(...routes)
|
|
203
|
+
* app.use(profilerErrorHandler())
|
|
204
|
+
* app.use(myAppErrorHandler)
|
|
205
|
+
*/
|
|
206
|
+
declare function profilerErrorHandler(): (err: Error, _req: Request, _res: Response, next: NextFunction) => void;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Wires the profiler's *presentation* layer onto an Express app: the
|
|
210
|
+
* Nunjucks view engine, static assets (CSS/JS) and the "/" landing page.
|
|
211
|
+
* The profiler's own data routes (list/detail/wdt) are registered
|
|
212
|
+
* separately via `createProfilerRouter(profiler)`, alongside the
|
|
213
|
+
* application's other feature routers — see README.md for the full
|
|
214
|
+
* integration example.
|
|
215
|
+
*
|
|
216
|
+
* Call this once, before `app.use(opticoreProfiler(...))` handles any
|
|
217
|
+
* request, so the view engine and static assets are ready.
|
|
218
|
+
*/
|
|
219
|
+
declare function registerProfilerViews(app: Application, profiler: OpticoreProfiler): void;
|
|
220
|
+
|
|
221
|
+
declare function createProfilerRouter(profiler: OpticoreProfiler): TFeatureRoutes;
|
|
222
|
+
|
|
223
|
+
type SqlArg = string | {
|
|
224
|
+
sql: string;
|
|
225
|
+
[key: string]: unknown;
|
|
226
|
+
};
|
|
227
|
+
type MakeQueryFn = (sql: SqlArg, values?: unknown[], callback?: (...args: unknown[]) => void) => Promise<unknown>;
|
|
228
|
+
interface MySQLDriverCtor {
|
|
229
|
+
prototype: {
|
|
230
|
+
makeQuery: MakeQueryFn;
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Decorates a MySQL driver's prototype so every query it runs is timed and
|
|
235
|
+
* recorded against the request currently active on the profiler's
|
|
236
|
+
* AsyncLocalStorage context — application code that calls `db.query(...)`
|
|
237
|
+
* or `db.makeQuery(...)` is completely unaware this happens. Call once at
|
|
238
|
+
* bootstrap, before any request is handled. Idempotent: safe to call more
|
|
239
|
+
* than once (e.g. under hot reload) — re-wrapping is a no-op.
|
|
240
|
+
*/
|
|
241
|
+
declare function instrumentMySQL(DriverClass: MySQLDriverCtor): void;
|
|
242
|
+
|
|
243
|
+
interface ILogSuccessArg {
|
|
244
|
+
title?: string;
|
|
245
|
+
message?: string;
|
|
246
|
+
}
|
|
247
|
+
interface ILogInfoArg {
|
|
248
|
+
title?: string;
|
|
249
|
+
message?: string;
|
|
250
|
+
}
|
|
251
|
+
interface ILogWarnArg {
|
|
252
|
+
title?: string;
|
|
253
|
+
message: string;
|
|
254
|
+
}
|
|
255
|
+
interface ILogErrorArg {
|
|
256
|
+
message: string;
|
|
257
|
+
title?: string;
|
|
258
|
+
errorType?: unknown;
|
|
259
|
+
stackTrace?: unknown;
|
|
260
|
+
httpCodeValue?: unknown;
|
|
261
|
+
}
|
|
262
|
+
interface LoggerCoreLike {
|
|
263
|
+
success(arg: ILogSuccessArg): void;
|
|
264
|
+
info(arg: ILogInfoArg): void;
|
|
265
|
+
warn(arg: ILogWarnArg): void;
|
|
266
|
+
error(arg: ILogErrorArg): void;
|
|
267
|
+
debug(message: string): void;
|
|
268
|
+
}
|
|
269
|
+
interface LoggerCoreCtor {
|
|
270
|
+
prototype: LoggerCoreLike;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Decorates LoggerCore's prototype so every log call the application makes
|
|
274
|
+
* (through its normal `logger.info(...)`, `logger.error(...)`, etc.) is
|
|
275
|
+
* mirrored into the active request's LoggerCollector, in addition to
|
|
276
|
+
* whatever the logger already does (console/file/remote transports keep
|
|
277
|
+
* working unchanged). Call once at bootstrap. Idempotent.
|
|
278
|
+
*/
|
|
279
|
+
declare function instrumentLogger(LoggerClass: LoggerCoreCtor): void;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Generates a short (default 8 chars) alphanumeric token by rejection-sampling
|
|
283
|
+
* random bytes against a 36-symbol alphabet, so every character is unbiased.
|
|
284
|
+
*/
|
|
285
|
+
declare function generateToken(length?: number): string;
|
|
286
|
+
declare function isValidToken(token: unknown): token is string;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Looks up the named collector for the request currently being handled on
|
|
290
|
+
* this async chain. Returns undefined outside of a profiled request (e.g.
|
|
291
|
+
* profiling disabled, or the call happened outside any HTTP request) —
|
|
292
|
+
* callers must treat that as "do nothing", never throw.
|
|
293
|
+
*/
|
|
294
|
+
declare function getActiveCollector<T extends DataCollector>(name: string): T | undefined;
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Circular buffer of the N most recent profiles, kept in process memory.
|
|
298
|
+
* Lost on restart — pick FileStorage when profiles need to survive that.
|
|
299
|
+
*/
|
|
300
|
+
declare class MemoryStorage implements ProfilerStorage {
|
|
301
|
+
private readonly limit;
|
|
302
|
+
private readonly profiles;
|
|
303
|
+
private readonly order;
|
|
304
|
+
constructor(limit?: number);
|
|
305
|
+
write(profile: Profile): void;
|
|
306
|
+
read(token: string): Profile | undefined;
|
|
307
|
+
find(limit?: number): Profile[];
|
|
308
|
+
purge(olderThanMs?: number): void;
|
|
309
|
+
clear(): void;
|
|
310
|
+
count(): number;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Persists profiles as JSON files under `<dir>/<token>.json`, plus a single
|
|
315
|
+
* `<dir>/index.json` holding lightweight metadata (no collector payloads)
|
|
316
|
+
* for the last N profiles, most-recent-first — reading the index to list
|
|
317
|
+
* profiles never has to open every per-token file, mirroring Symfony's
|
|
318
|
+
* FileProfilerStorage split between index and full data.
|
|
319
|
+
*/
|
|
320
|
+
declare class FileStorage implements ProfilerStorage {
|
|
321
|
+
private readonly dir;
|
|
322
|
+
private readonly maxIndexSize;
|
|
323
|
+
private readonly indexPath;
|
|
324
|
+
constructor(dir?: string, maxIndexSize?: number);
|
|
325
|
+
private ensureDir;
|
|
326
|
+
private profilePath;
|
|
327
|
+
private readIndex;
|
|
328
|
+
private writeIndex;
|
|
329
|
+
write(profile: Profile): void;
|
|
330
|
+
read(token: string): Profile | undefined;
|
|
331
|
+
find(limit?: number): Profile[];
|
|
332
|
+
purge(olderThanMs?: number): void;
|
|
333
|
+
clear(): void;
|
|
334
|
+
private deleteFile;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Masks sensitive values before they are persisted or rendered. Applied to
|
|
339
|
+
* headers, cookies, query and body objects captured by RequestCollector —
|
|
340
|
+
* masking happens at collection time (not at render time) so redacted data
|
|
341
|
+
* never even reaches disk in FileStorage.
|
|
342
|
+
*/
|
|
343
|
+
declare class Masker {
|
|
344
|
+
private readonly keys;
|
|
345
|
+
constructor(sensitiveKeys?: readonly string[]);
|
|
346
|
+
isSensitive(key: string): boolean;
|
|
347
|
+
/** Shallow-masks a flat object (headers, query, cookies, single-level body). */
|
|
348
|
+
maskShallow<T extends Record<string, unknown>>(obj: T | undefined | null): T;
|
|
349
|
+
/** Recursively masks nested objects/arrays (request/response bodies). */
|
|
350
|
+
maskDeep(value: unknown, depth?: number): unknown;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
interface RequestCollectorData {
|
|
354
|
+
method: string;
|
|
355
|
+
url: string;
|
|
356
|
+
statusCode: number;
|
|
357
|
+
statusMessage: string;
|
|
358
|
+
request: IRequestInfo;
|
|
359
|
+
response: IResponseInfo;
|
|
360
|
+
route: IRouteInfo;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Observes the request/response objects purely by reading their already-public
|
|
364
|
+
* properties inside collect() — nothing here touches how the application
|
|
365
|
+
* builds its request or sends its response.
|
|
366
|
+
*/
|
|
367
|
+
declare class RequestCollector implements DataCollector<RequestCollectorData> {
|
|
368
|
+
private readonly masker;
|
|
369
|
+
private data;
|
|
370
|
+
constructor(masker?: Masker);
|
|
371
|
+
getName(): string;
|
|
372
|
+
/** Parses the buffered response body as JSON when the response declared a JSON content-type — undefined otherwise (HTML pages, empty bodies, non-JSON payloads), so the detail view falls back to its "no body captured" state instead of dumping raw bytes. */
|
|
373
|
+
private parseResponseBody;
|
|
374
|
+
/** Parses Set-Cookie response header(s) into name -> value, mirroring how request cookies are captured — masked per cookie name (Masker), not blanket-redacted like the raw "set-cookie" header. */
|
|
375
|
+
private parseResponseCookies;
|
|
376
|
+
/**
|
|
377
|
+
* Reads `req.session` (express-session or any middleware that assigns a
|
|
378
|
+
* plain, JSON-serializable object there) — own data properties only,
|
|
379
|
+
* methods (save/destroy/reload/...) live on the prototype so a plain
|
|
380
|
+
* Object.entries() already excludes them. Returns undefined (not `{}`)
|
|
381
|
+
* when there's no session object at all, so the Session tab can tell
|
|
382
|
+
* "this app doesn't use sessions" apart from "session exists but empty".
|
|
383
|
+
*/
|
|
384
|
+
private parseSession;
|
|
385
|
+
collect(ctx: CollectorContext): void;
|
|
386
|
+
getData(): RequestCollectorData;
|
|
387
|
+
reset(): void;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
interface TimePhase {
|
|
391
|
+
name: string;
|
|
392
|
+
/** ms elapsed since the request started. */
|
|
393
|
+
offset: number;
|
|
394
|
+
}
|
|
395
|
+
interface TimeCollectorData {
|
|
396
|
+
startedAt: number;
|
|
397
|
+
duration: number;
|
|
398
|
+
phases: TimePhase[];
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Total wall-clock duration plus any named checkpoints other code marks
|
|
402
|
+
* along the way (e.g. "middleware.done", "handler.done") via mark().
|
|
403
|
+
* mark() is looked up through the request's AsyncLocalStorage-scoped
|
|
404
|
+
* collector instance (context.getActiveCollector), so it never requires
|
|
405
|
+
* threading a token through business code.
|
|
406
|
+
*/
|
|
407
|
+
declare class TimeCollector implements DataCollector<TimeCollectorData> {
|
|
408
|
+
private data;
|
|
409
|
+
private readonly phases;
|
|
410
|
+
getName(): string;
|
|
411
|
+
/** Records a named checkpoint at the current elapsed time, relative to the active request's start. */
|
|
412
|
+
mark(name: string): void;
|
|
413
|
+
collect(ctx: CollectorContext): void;
|
|
414
|
+
getData(): TimeCollectorData;
|
|
415
|
+
reset(): void;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
interface MemoryCollectorData {
|
|
419
|
+
heapUsedBefore: number;
|
|
420
|
+
heapUsedAfter: number;
|
|
421
|
+
delta: number;
|
|
422
|
+
}
|
|
423
|
+
/** Heap usage before the request started vs. right after the response finished. */
|
|
424
|
+
declare class MemoryCollector implements DataCollector<MemoryCollectorData> {
|
|
425
|
+
private data;
|
|
426
|
+
getName(): string;
|
|
427
|
+
collect(ctx: CollectorContext): void;
|
|
428
|
+
getData(): MemoryCollectorData;
|
|
429
|
+
reset(): void;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Holds the SQL queries recorded for one request. Instances are never
|
|
434
|
+
* populated by business code directly — see instrumentation/mysql.instrumentation.ts,
|
|
435
|
+
* which decorates the DB driver once at bootstrap and pushes into whichever
|
|
436
|
+
* DatabaseCollector is active on the current AsyncLocalStorage context.
|
|
437
|
+
*/
|
|
438
|
+
declare class DatabaseCollector implements DataCollector<ISqlQuery[]> {
|
|
439
|
+
private readonly queries;
|
|
440
|
+
getName(): string;
|
|
441
|
+
record(query: Omit<ISqlQuery, "timestamp">): void;
|
|
442
|
+
collect(_ctx: CollectorContext): void;
|
|
443
|
+
getData(): ISqlQuery[];
|
|
444
|
+
reset(): void;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Holds the log entries emitted for one request, grouped implicitly by level
|
|
449
|
+
* (the view layer groups/counts them). Like DatabaseCollector, entries are
|
|
450
|
+
* pushed in real time by instrumentation/logger.instrumentation.ts — business
|
|
451
|
+
* code keeps calling `logger.info(...)` / `logger.error(...)` unmodified.
|
|
452
|
+
*/
|
|
453
|
+
declare class LoggerCollector implements DataCollector<ILogEntry[]> {
|
|
454
|
+
private readonly logs;
|
|
455
|
+
getName(): string;
|
|
456
|
+
record(entry: Omit<ILogEntry, "timestamp">): void;
|
|
457
|
+
collect(_ctx: CollectorContext): void;
|
|
458
|
+
getData(): ILogEntry[];
|
|
459
|
+
byLevel(level: ILogEntry["level"]): ILogEntry[];
|
|
460
|
+
reset(): void;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
interface ExceptionData {
|
|
464
|
+
name: string;
|
|
465
|
+
message: string;
|
|
466
|
+
stack: string;
|
|
467
|
+
}
|
|
468
|
+
interface ExceptionCollectorData {
|
|
469
|
+
error: ExceptionData | null;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Captures the error that made a request fail, if any. Fed by:
|
|
473
|
+
* - middleware/errorHandler.middleware.ts, an Express error-handling
|
|
474
|
+
* middleware the app mounts after its routes (standard Express wiring —
|
|
475
|
+
* not a change to business logic, same as mounting any other middleware).
|
|
476
|
+
* - the process-level uncaughtException/unhandledRejection fallback, for
|
|
477
|
+
* errors that escape Express's error-handling chain entirely.
|
|
478
|
+
*/
|
|
479
|
+
declare class ExceptionCollector implements DataCollector<ExceptionCollectorData> {
|
|
480
|
+
private error;
|
|
481
|
+
getName(): string;
|
|
482
|
+
setError(err: Error): void;
|
|
483
|
+
collect(_ctx: CollectorContext, error?: Error | null): void;
|
|
484
|
+
getData(): ExceptionCollectorData;
|
|
485
|
+
reset(): void;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export { type CollectorContext, type CollectorFactory, CollectorRegistry, DEFAULT_EXCLUDE_PATHS, DEFAULT_SENSITIVE_KEYS, type DataCollector, DatabaseCollector, ExceptionCollector, FileStorage, LoggerCollector, MemoryCollector, MemoryStorage, type OpticoreProfiler, type Profile, type ProfilerOptions, type ProfilerStorage, RequestCollector, type SecurityConfig, TimeCollector, createProfilerRouter, generateToken, getActiveCollector, instrumentLogger, instrumentMySQL, isValidToken, opticoreProfiler, profilerErrorHandler, registerProfilerViews };
|