adonisjs-server-stats 1.1.4 → 1.2.2
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/README.md +58 -1
- package/dist/src/controller/debug_controller.d.ts +2 -0
- package/dist/src/controller/debug_controller.d.ts.map +1 -1
- package/dist/src/controller/debug_controller.js +23 -0
- package/dist/src/debug/debug_store.d.ts +2 -0
- package/dist/src/debug/debug_store.d.ts.map +1 -1
- package/dist/src/debug/debug_store.js +11 -0
- package/dist/src/debug/trace_collector.d.ts +51 -0
- package/dist/src/debug/trace_collector.d.ts.map +1 -0
- package/dist/src/debug/trace_collector.js +181 -0
- package/dist/src/debug/types.d.ts +52 -0
- package/dist/src/debug/types.d.ts.map +1 -1
- package/dist/src/edge/client/debug-panel.css +120 -2
- package/dist/src/edge/client/debug-panel.js +193 -2
- package/dist/src/edge/client/stats-bar.css +3 -3
- package/dist/src/edge/plugin.d.ts.map +1 -1
- package/dist/src/edge/plugin.js +1 -0
- package/dist/src/edge/views/debug-panel.edge +26 -3
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/middleware/request_tracking_middleware.d.ts +2 -0
- package/dist/src/middleware/request_tracking_middleware.d.ts.map +1 -1
- package/dist/src/middleware/request_tracking_middleware.js +22 -6
- package/dist/src/provider/server_stats_provider.d.ts.map +1 -1
- package/dist/src/provider/server_stats_provider.js +7 -1
- package/dist/src/types.d.ts +15 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ Zero frontend dependencies. Zero build step. Just `@serverStats()` and go.
|
|
|
35
35
|
|
|
36
36
|
- **Live stats bar** -- CPU, memory, event loop lag, HTTP throughput, DB pool, Redis, queues, logs
|
|
37
37
|
- **Debug toolbar** -- SQL queries, events, emails, routes, logs with search and filtering
|
|
38
|
+
- **Request tracing** -- per-request waterfall timeline showing DB queries, events, and custom spans
|
|
38
39
|
- **Custom panes** -- add your own tabs (webhooks, emails, cache, anything) with a simple config
|
|
39
40
|
- **Pluggable collectors** -- use built-in collectors or write your own
|
|
40
41
|
- **Visibility control** -- show only to admins, specific roles, or in dev mode
|
|
@@ -223,6 +224,8 @@ export default class ServerStatsController {
|
|
|
223
224
|
| `maxEmails` | `number` | `100` | Max emails to buffer |
|
|
224
225
|
| `slowQueryThresholdMs` | `number` | `100` | Slow query threshold (ms) |
|
|
225
226
|
| `persistDebugData` | `boolean \| string` | `false` | Persist debug data to disk across restarts. `true` writes to `.adonisjs/server-stats/debug-data.json`, or pass a custom path. |
|
|
227
|
+
| `tracing` | `boolean` | `false` | Enable per-request tracing with timeline visualization |
|
|
228
|
+
| `maxTraces` | `number` | `200` | Max request traces to buffer |
|
|
226
229
|
| `panes` | `DebugPane[]` | -- | Custom debug panel tabs |
|
|
227
230
|
|
|
228
231
|
---
|
|
@@ -372,7 +375,7 @@ Features:
|
|
|
372
375
|
|
|
373
376
|
## Dev Toolbar
|
|
374
377
|
|
|
375
|
-
Adds a debug panel with SQL query inspection, event tracking, email capture with HTML preview, route table, and
|
|
378
|
+
Adds a debug panel with SQL query inspection, event tracking, email capture with HTML preview, route table, live logs, and per-request tracing. Only active in non-production environments.
|
|
376
379
|
|
|
377
380
|
```ts
|
|
378
381
|
export default defineConfig({
|
|
@@ -383,6 +386,7 @@ export default defineConfig({
|
|
|
383
386
|
maxEmails: 100,
|
|
384
387
|
slowQueryThresholdMs: 100,
|
|
385
388
|
persistDebugData: true, // or a custom path: 'custom/debug.json'
|
|
389
|
+
tracing: true, // enable per-request timeline
|
|
386
390
|
},
|
|
387
391
|
})
|
|
388
392
|
```
|
|
@@ -398,6 +402,8 @@ router
|
|
|
398
402
|
router.get('routes', '#controllers/admin/debug_controller.routes')
|
|
399
403
|
router.get('emails', '#controllers/admin/debug_controller.emails')
|
|
400
404
|
router.get('emails/:id/preview', '#controllers/admin/debug_controller.emailPreview')
|
|
405
|
+
router.get('traces', '#controllers/admin/debug_controller.traces')
|
|
406
|
+
router.get('traces/:id', '#controllers/admin/debug_controller.traceDetail')
|
|
401
407
|
})
|
|
402
408
|
.prefix('/admin/api/debug')
|
|
403
409
|
.use(middleware.admin())
|
|
@@ -414,6 +420,52 @@ Enable `persistDebugData: true` to save queries, events, and emails to `.adonisj
|
|
|
414
420
|
- **Flushed** every 30 seconds (handles crashes)
|
|
415
421
|
- **Saved** on graceful shutdown
|
|
416
422
|
|
|
423
|
+
### Request Tracing
|
|
424
|
+
|
|
425
|
+
When `tracing: true` is set, the debug panel gains a **Timeline** tab that shows a waterfall view of every HTTP request -- which DB queries ran, in what order, and how long each took.
|
|
426
|
+
|
|
427
|
+
Tracing uses `AsyncLocalStorage` to automatically correlate operations to the request that triggered them. DB queries captured via `db:query` events and `console.warn` calls are automatically attached to the active request trace.
|
|
428
|
+
|
|
429
|
+
#### How it works
|
|
430
|
+
|
|
431
|
+
```
|
|
432
|
+
GET /organizations/create 286ms
|
|
433
|
+
├─ SELECT * FROM users 2ms █
|
|
434
|
+
├─ SELECT * FROM orgs 4ms █
|
|
435
|
+
├─ fetchMembers (custom) 180ms ██████████████████
|
|
436
|
+
└─ response sent 5ms ██
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
1. The **Timeline** tab shows a list of recent requests with method, URL, status code, duration, span count, and any warnings
|
|
440
|
+
2. Click a request to see the **waterfall chart** -- each span is a horizontal bar positioned by time offset, color-coded by category
|
|
441
|
+
3. Spans can be nested (a custom span wrapping DB queries will show them indented)
|
|
442
|
+
|
|
443
|
+
#### Span categories
|
|
444
|
+
|
|
445
|
+
| Category | Color | Auto-captured |
|
|
446
|
+
|----------|--------|---------------|
|
|
447
|
+
| DB | Purple | `db:query` events |
|
|
448
|
+
| Request | Blue | Full request lifecycle |
|
|
449
|
+
| Mail | Green | -- |
|
|
450
|
+
| Event | Amber | -- |
|
|
451
|
+
| View | Cyan | -- |
|
|
452
|
+
| Custom | Gray | Via `trace()` helper |
|
|
453
|
+
|
|
454
|
+
#### Custom spans
|
|
455
|
+
|
|
456
|
+
Use the `trace()` helper to wrap any async code in a named span:
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
import { trace } from 'adonisjs-server-stats'
|
|
460
|
+
|
|
461
|
+
// In a controller or service:
|
|
462
|
+
const result = await trace('organization.fetchMembers', async () => {
|
|
463
|
+
return OrganizationService.getMembers(orgId)
|
|
464
|
+
})
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
If tracing is disabled or no request is active, `trace()` executes the function directly with no overhead.
|
|
468
|
+
|
|
417
469
|
### Custom Debug Panes
|
|
418
470
|
|
|
419
471
|
Add custom tabs to the debug panel:
|
|
@@ -573,8 +625,13 @@ import type {
|
|
|
573
625
|
EventRecord,
|
|
574
626
|
EmailRecord,
|
|
575
627
|
RouteRecord,
|
|
628
|
+
TraceSpan,
|
|
629
|
+
TraceRecord,
|
|
576
630
|
} from 'adonisjs-server-stats'
|
|
577
631
|
|
|
632
|
+
// Trace helper
|
|
633
|
+
import { trace } from 'adonisjs-server-stats'
|
|
634
|
+
|
|
578
635
|
// Collector option types
|
|
579
636
|
import type {
|
|
580
637
|
HttpCollectorOptions,
|
|
@@ -8,5 +8,7 @@ export default class DebugController {
|
|
|
8
8
|
routes({ response }: HttpContext): Promise<void>;
|
|
9
9
|
emails({ response }: HttpContext): Promise<void>;
|
|
10
10
|
emailPreview({ params, response }: HttpContext): Promise<void>;
|
|
11
|
+
traces({ response }: HttpContext): Promise<void>;
|
|
12
|
+
traceDetail({ params, response }: HttpContext): Promise<void>;
|
|
11
13
|
}
|
|
12
14
|
//# sourceMappingURL=debug_controller.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug_controller.d.ts","sourceRoot":"","sources":["../../../src/controller/debug_controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,MAAM,CAAC,OAAO,OAAO,eAAe;IACtB,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,UAAU;IAE/B,OAAO,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAMjC,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAKhC,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAKhC,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAOhC,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,WAAW;
|
|
1
|
+
{"version":3,"file":"debug_controller.d.ts","sourceRoot":"","sources":["../../../src/controller/debug_controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,MAAM,CAAC,OAAO,OAAO,eAAe;IACtB,OAAO,CAAC,KAAK;gBAAL,KAAK,EAAE,UAAU;IAE/B,OAAO,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAMjC,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAKhC,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAKhC,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAOhC,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,WAAW;IAS9C,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAahC,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,WAAW;CAWpD"}
|
|
@@ -30,4 +30,27 @@ export default class DebugController {
|
|
|
30
30
|
}
|
|
31
31
|
return response.header('Content-Type', 'text/html; charset=utf-8').send(html);
|
|
32
32
|
}
|
|
33
|
+
async traces({ response }) {
|
|
34
|
+
if (!this.store.traces) {
|
|
35
|
+
return response.json({ traces: [], total: 0 });
|
|
36
|
+
}
|
|
37
|
+
const traces = this.store.traces.getLatest(100);
|
|
38
|
+
// Strip spans from list view to keep it lightweight
|
|
39
|
+
const list = traces.map(({ spans, warnings, ...rest }) => ({
|
|
40
|
+
...rest,
|
|
41
|
+
warningCount: warnings.length,
|
|
42
|
+
}));
|
|
43
|
+
return response.json({ traces: list, total: this.store.traces.getTotalCount() });
|
|
44
|
+
}
|
|
45
|
+
async traceDetail({ params, response }) {
|
|
46
|
+
if (!this.store.traces) {
|
|
47
|
+
return response.notFound({ error: 'Tracing not enabled' });
|
|
48
|
+
}
|
|
49
|
+
const id = Number(params.id);
|
|
50
|
+
const trace = this.store.traces.getTrace(id);
|
|
51
|
+
if (!trace) {
|
|
52
|
+
return response.notFound({ error: 'Trace not found' });
|
|
53
|
+
}
|
|
54
|
+
return response.json(trace);
|
|
55
|
+
}
|
|
33
56
|
}
|
|
@@ -2,6 +2,7 @@ import { QueryCollector } from "./query_collector.js";
|
|
|
2
2
|
import { EventCollector } from "./event_collector.js";
|
|
3
3
|
import { EmailCollector } from "./email_collector.js";
|
|
4
4
|
import { RouteInspector } from "./route_inspector.js";
|
|
5
|
+
import { TraceCollector } from "./trace_collector.js";
|
|
5
6
|
import type { DevToolbarConfig } from "./types.js";
|
|
6
7
|
/**
|
|
7
8
|
* Singleton store holding all debug data collectors.
|
|
@@ -12,6 +13,7 @@ export declare class DebugStore {
|
|
|
12
13
|
readonly events: EventCollector;
|
|
13
14
|
readonly emails: EmailCollector;
|
|
14
15
|
readonly routes: RouteInspector;
|
|
16
|
+
readonly traces: TraceCollector | null;
|
|
15
17
|
constructor(config: DevToolbarConfig);
|
|
16
18
|
start(emitter: any, router: any): Promise<void>;
|
|
17
19
|
stop(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug_store.d.ts","sourceRoot":"","sources":["../../../src/debug/debug_store.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;GAGG;AACH,qBAAa,UAAU;IACrB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"debug_store.d.ts","sourceRoot":"","sources":["../../../src/debug/debug_store.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;GAGG;AACH,qBAAa,UAAU;IACrB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;gBAE3B,MAAM,EAAE,gBAAgB;IAW9B,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAQrD,IAAI,IAAI,IAAI;IAOZ,kEAAkE;IAC5D,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBjD,uDAAuD;IACjD,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAuBpD"}
|
|
@@ -4,6 +4,7 @@ import { QueryCollector } from "./query_collector.js";
|
|
|
4
4
|
import { EventCollector } from "./event_collector.js";
|
|
5
5
|
import { EmailCollector } from "./email_collector.js";
|
|
6
6
|
import { RouteInspector } from "./route_inspector.js";
|
|
7
|
+
import { TraceCollector } from "./trace_collector.js";
|
|
7
8
|
/**
|
|
8
9
|
* Singleton store holding all debug data collectors.
|
|
9
10
|
* Bound to the AdonisJS container as `debug.store`.
|
|
@@ -13,22 +14,26 @@ export class DebugStore {
|
|
|
13
14
|
events;
|
|
14
15
|
emails;
|
|
15
16
|
routes;
|
|
17
|
+
traces;
|
|
16
18
|
constructor(config) {
|
|
17
19
|
this.queries = new QueryCollector(config.maxQueries, config.slowQueryThresholdMs);
|
|
18
20
|
this.events = new EventCollector(config.maxEvents);
|
|
19
21
|
this.emails = new EmailCollector(config.maxEmails);
|
|
20
22
|
this.routes = new RouteInspector();
|
|
23
|
+
this.traces = config.tracing ? new TraceCollector(config.maxTraces) : null;
|
|
21
24
|
}
|
|
22
25
|
async start(emitter, router) {
|
|
23
26
|
await this.queries.start(emitter);
|
|
24
27
|
this.events.start(emitter);
|
|
25
28
|
await this.emails.start(emitter);
|
|
26
29
|
this.routes.inspect(router);
|
|
30
|
+
this.traces?.start(emitter);
|
|
27
31
|
}
|
|
28
32
|
stop() {
|
|
29
33
|
this.queries.stop();
|
|
30
34
|
this.events.stop();
|
|
31
35
|
this.emails.stop();
|
|
36
|
+
this.traces?.stop();
|
|
32
37
|
}
|
|
33
38
|
/** Serialize all collector data to a JSON file (atomic write). */
|
|
34
39
|
async saveToDisk(filePath) {
|
|
@@ -37,6 +42,9 @@ export class DebugStore {
|
|
|
37
42
|
events: this.events.getEvents(),
|
|
38
43
|
emails: this.emails.getEmails(),
|
|
39
44
|
};
|
|
45
|
+
if (this.traces) {
|
|
46
|
+
data.traces = this.traces.getTraces();
|
|
47
|
+
}
|
|
40
48
|
const json = JSON.stringify(data);
|
|
41
49
|
const tmpPath = filePath + ".tmp";
|
|
42
50
|
await mkdir(dirname(filePath), { recursive: true });
|
|
@@ -62,5 +70,8 @@ export class DebugStore {
|
|
|
62
70
|
if (Array.isArray(data.emails) && data.emails.length > 0) {
|
|
63
71
|
this.emails.loadRecords(data.emails);
|
|
64
72
|
}
|
|
73
|
+
if (this.traces && Array.isArray(data.traces) && data.traces.length > 0) {
|
|
74
|
+
this.traces.loadRecords(data.traces);
|
|
75
|
+
}
|
|
65
76
|
}
|
|
66
77
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { TraceSpan, TraceRecord } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wrap an async function in a traced span.
|
|
4
|
+
*
|
|
5
|
+
* If tracing is not enabled or no request is active, the function
|
|
6
|
+
* is executed directly without overhead.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { trace } from 'adonisjs-server-stats'
|
|
11
|
+
*
|
|
12
|
+
* const result = await trace('fetchMembers', async () => {
|
|
13
|
+
* return OrganizationService.getMembers(orgId)
|
|
14
|
+
* })
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function trace<T>(label: string, fn: () => Promise<T>): Promise<T>;
|
|
18
|
+
/**
|
|
19
|
+
* Collects per-request traces using AsyncLocalStorage.
|
|
20
|
+
*
|
|
21
|
+
* Automatically captures DB queries and console.warn calls within
|
|
22
|
+
* the request context. Users can add custom spans via {@link trace}.
|
|
23
|
+
*/
|
|
24
|
+
export declare class TraceCollector {
|
|
25
|
+
private buffer;
|
|
26
|
+
private als;
|
|
27
|
+
private emitter;
|
|
28
|
+
private dbHandler;
|
|
29
|
+
private originalConsoleWarn;
|
|
30
|
+
constructor(maxTraces?: number);
|
|
31
|
+
/** Start a new trace context for an HTTP request. */
|
|
32
|
+
startTrace(callback: () => Promise<void>): Promise<void>;
|
|
33
|
+
/** Finish the current trace and save it to the ring buffer. */
|
|
34
|
+
finishTrace(method: string, url: string, statusCode: number): void;
|
|
35
|
+
/** Add a span to the current trace (if active). */
|
|
36
|
+
addSpan(label: string, category: TraceSpan['category'], startOffset: number, duration: number, metadata?: Record<string, any>): void;
|
|
37
|
+
/** Wrap a function in a traced span with automatic nesting. */
|
|
38
|
+
span<T>(label: string, category: TraceSpan['category'], fn: () => Promise<T>): Promise<T>;
|
|
39
|
+
/** Hook into db:query events and console.warn to auto-create spans. */
|
|
40
|
+
start(emitter: any): void;
|
|
41
|
+
/** Unhook event listeners and restore console.warn. */
|
|
42
|
+
stop(): void;
|
|
43
|
+
getTraces(): TraceRecord[];
|
|
44
|
+
getLatest(n: number): TraceRecord[];
|
|
45
|
+
getTrace(id: number): TraceRecord | undefined;
|
|
46
|
+
getTotalCount(): number;
|
|
47
|
+
clear(): void;
|
|
48
|
+
/** Restore persisted records into the buffer and reset the ID counter. */
|
|
49
|
+
loadRecords(records: TraceRecord[]): void;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=trace_collector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace_collector.d.ts","sourceRoot":"","sources":["../../../src/debug/trace_collector.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAmBxD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAG9E;AAED;;;;;GAKG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,GAAG,CAAwC;IACnD,OAAO,CAAC,OAAO,CAAY;IAC3B,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,mBAAmB,CAAmC;gBAElD,SAAS,GAAE,MAAY;IAKnC,qDAAqD;IACrD,UAAU,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxD,+DAA+D;IAC/D,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAqBlE,mDAAmD;IACnD,OAAO,CACL,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,SAAS,CAAC,UAAU,CAAC,EAC/B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC7B,IAAI;IAeP,+DAA+D;IACzD,IAAI,CAAC,CAAC,EACV,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,SAAS,CAAC,UAAU,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC;IAyBb,uEAAuE;IACvE,KAAK,CAAC,OAAO,EAAE,GAAG,GAAG,IAAI;IAsCzB,uDAAuD;IACvD,IAAI,IAAI,IAAI;IAaZ,SAAS,IAAI,WAAW,EAAE;IAI1B,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,WAAW,EAAE;IAInC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAI7C,aAAa,IAAI,MAAM;IAIvB,KAAK,IAAI,IAAI;IAIb,0EAA0E;IAC1E,WAAW,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI;CAK1C"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { performance } from 'node:perf_hooks';
|
|
3
|
+
import { RingBuffer } from './ring_buffer.js';
|
|
4
|
+
/**
|
|
5
|
+
* Module-level singleton reference for the `trace()` helper.
|
|
6
|
+
*/
|
|
7
|
+
let globalTraceCollector = null;
|
|
8
|
+
/**
|
|
9
|
+
* Wrap an async function in a traced span.
|
|
10
|
+
*
|
|
11
|
+
* If tracing is not enabled or no request is active, the function
|
|
12
|
+
* is executed directly without overhead.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { trace } from 'adonisjs-server-stats'
|
|
17
|
+
*
|
|
18
|
+
* const result = await trace('fetchMembers', async () => {
|
|
19
|
+
* return OrganizationService.getMembers(orgId)
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export async function trace(label, fn) {
|
|
24
|
+
if (!globalTraceCollector)
|
|
25
|
+
return fn();
|
|
26
|
+
return globalTraceCollector.span(label, 'custom', fn);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Collects per-request traces using AsyncLocalStorage.
|
|
30
|
+
*
|
|
31
|
+
* Automatically captures DB queries and console.warn calls within
|
|
32
|
+
* the request context. Users can add custom spans via {@link trace}.
|
|
33
|
+
*/
|
|
34
|
+
export class TraceCollector {
|
|
35
|
+
buffer;
|
|
36
|
+
als = new AsyncLocalStorage();
|
|
37
|
+
emitter = null;
|
|
38
|
+
dbHandler = null;
|
|
39
|
+
originalConsoleWarn = null;
|
|
40
|
+
constructor(maxTraces = 200) {
|
|
41
|
+
this.buffer = new RingBuffer(maxTraces);
|
|
42
|
+
globalTraceCollector = this;
|
|
43
|
+
}
|
|
44
|
+
/** Start a new trace context for an HTTP request. */
|
|
45
|
+
startTrace(callback) {
|
|
46
|
+
const ctx = {
|
|
47
|
+
requestStart: performance.now(),
|
|
48
|
+
spans: [],
|
|
49
|
+
warnings: [],
|
|
50
|
+
nextSpanId: 1,
|
|
51
|
+
currentSpanId: null,
|
|
52
|
+
};
|
|
53
|
+
return this.als.run(ctx, callback);
|
|
54
|
+
}
|
|
55
|
+
/** Finish the current trace and save it to the ring buffer. */
|
|
56
|
+
finishTrace(method, url, statusCode) {
|
|
57
|
+
const ctx = this.als.getStore();
|
|
58
|
+
if (!ctx)
|
|
59
|
+
return;
|
|
60
|
+
const totalDuration = performance.now() - ctx.requestStart;
|
|
61
|
+
const record = {
|
|
62
|
+
id: this.buffer.getNextId(),
|
|
63
|
+
method,
|
|
64
|
+
url,
|
|
65
|
+
statusCode,
|
|
66
|
+
totalDuration: Math.round(totalDuration * 100) / 100,
|
|
67
|
+
spanCount: ctx.spans.length,
|
|
68
|
+
spans: ctx.spans,
|
|
69
|
+
warnings: ctx.warnings,
|
|
70
|
+
timestamp: Date.now(),
|
|
71
|
+
};
|
|
72
|
+
this.buffer.push(record);
|
|
73
|
+
}
|
|
74
|
+
/** Add a span to the current trace (if active). */
|
|
75
|
+
addSpan(label, category, startOffset, duration, metadata) {
|
|
76
|
+
const ctx = this.als.getStore();
|
|
77
|
+
if (!ctx)
|
|
78
|
+
return;
|
|
79
|
+
ctx.spans.push({
|
|
80
|
+
id: String(ctx.nextSpanId++),
|
|
81
|
+
parentId: ctx.currentSpanId,
|
|
82
|
+
label,
|
|
83
|
+
category,
|
|
84
|
+
startOffset: Math.round(startOffset * 100) / 100,
|
|
85
|
+
duration: Math.round(duration * 100) / 100,
|
|
86
|
+
metadata,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/** Wrap a function in a traced span with automatic nesting. */
|
|
90
|
+
async span(label, category, fn) {
|
|
91
|
+
const ctx = this.als.getStore();
|
|
92
|
+
if (!ctx)
|
|
93
|
+
return fn();
|
|
94
|
+
const start = performance.now();
|
|
95
|
+
const parentId = ctx.currentSpanId;
|
|
96
|
+
const spanId = String(ctx.nextSpanId++);
|
|
97
|
+
ctx.currentSpanId = spanId;
|
|
98
|
+
try {
|
|
99
|
+
return await fn();
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
const duration = performance.now() - start;
|
|
103
|
+
ctx.spans.push({
|
|
104
|
+
id: spanId,
|
|
105
|
+
parentId,
|
|
106
|
+
label,
|
|
107
|
+
category,
|
|
108
|
+
startOffset: Math.round((start - ctx.requestStart) * 100) / 100,
|
|
109
|
+
duration: Math.round(duration * 100) / 100,
|
|
110
|
+
});
|
|
111
|
+
ctx.currentSpanId = parentId;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Hook into db:query events and console.warn to auto-create spans. */
|
|
115
|
+
start(emitter) {
|
|
116
|
+
this.emitter = emitter;
|
|
117
|
+
if (emitter && typeof emitter.on === 'function') {
|
|
118
|
+
this.dbHandler = (data) => {
|
|
119
|
+
const ctx = this.als.getStore();
|
|
120
|
+
if (!ctx)
|
|
121
|
+
return;
|
|
122
|
+
const duration = typeof data.duration === 'number'
|
|
123
|
+
? data.duration
|
|
124
|
+
: Array.isArray(data.duration)
|
|
125
|
+
? data.duration[0] * 1e3 + data.duration[1] / 1e6
|
|
126
|
+
: 0;
|
|
127
|
+
const offset = performance.now() - ctx.requestStart - duration;
|
|
128
|
+
this.addSpan(data.sql || 'query', 'db', offset, duration, {
|
|
129
|
+
method: data.method,
|
|
130
|
+
model: data.model,
|
|
131
|
+
connection: data.connection,
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
emitter.on('db:query', this.dbHandler);
|
|
135
|
+
}
|
|
136
|
+
// Intercept console.warn to capture warnings per-request
|
|
137
|
+
this.originalConsoleWarn = console.warn;
|
|
138
|
+
const self = this;
|
|
139
|
+
console.warn = function (...args) {
|
|
140
|
+
const ctx = self.als.getStore();
|
|
141
|
+
if (ctx) {
|
|
142
|
+
ctx.warnings.push(args.map(String).join(' '));
|
|
143
|
+
}
|
|
144
|
+
self.originalConsoleWarn.apply(console, args);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/** Unhook event listeners and restore console.warn. */
|
|
148
|
+
stop() {
|
|
149
|
+
if (this.emitter && this.dbHandler) {
|
|
150
|
+
this.emitter.off('db:query', this.dbHandler);
|
|
151
|
+
}
|
|
152
|
+
if (this.originalConsoleWarn) {
|
|
153
|
+
console.warn = this.originalConsoleWarn;
|
|
154
|
+
}
|
|
155
|
+
this.dbHandler = null;
|
|
156
|
+
this.emitter = null;
|
|
157
|
+
this.originalConsoleWarn = null;
|
|
158
|
+
globalTraceCollector = null;
|
|
159
|
+
}
|
|
160
|
+
getTraces() {
|
|
161
|
+
return this.buffer.toArray();
|
|
162
|
+
}
|
|
163
|
+
getLatest(n) {
|
|
164
|
+
return this.buffer.latest(n);
|
|
165
|
+
}
|
|
166
|
+
getTrace(id) {
|
|
167
|
+
return this.buffer.toArray().find((t) => t.id === id);
|
|
168
|
+
}
|
|
169
|
+
getTotalCount() {
|
|
170
|
+
return this.buffer.size();
|
|
171
|
+
}
|
|
172
|
+
clear() {
|
|
173
|
+
this.buffer.clear();
|
|
174
|
+
}
|
|
175
|
+
/** Restore persisted records into the buffer and reset the ID counter. */
|
|
176
|
+
loadRecords(records) {
|
|
177
|
+
this.buffer.load(records);
|
|
178
|
+
const maxId = records.reduce((m, r) => Math.max(m, r.id), 0);
|
|
179
|
+
this.buffer.setNextId(maxId + 1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -96,6 +96,54 @@ export interface RouteRecord {
|
|
|
96
96
|
*/
|
|
97
97
|
middleware: string[];
|
|
98
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* A single span within a request trace.
|
|
101
|
+
*
|
|
102
|
+
* Represents a timed operation (DB query, middleware, custom code block)
|
|
103
|
+
* that occurred during an HTTP request. Spans can be nested via `parentId`.
|
|
104
|
+
*/
|
|
105
|
+
export interface TraceSpan {
|
|
106
|
+
/** Unique span ID within the trace. */
|
|
107
|
+
id: string;
|
|
108
|
+
/** Parent span ID, or `null` for root-level spans. */
|
|
109
|
+
parentId: string | null;
|
|
110
|
+
/** Human-readable label (e.g. `"SELECT * FROM users"`, `"auth middleware"`). */
|
|
111
|
+
label: string;
|
|
112
|
+
/** Category for color-coding and grouping in the timeline. */
|
|
113
|
+
category: 'request' | 'middleware' | 'db' | 'view' | 'mail' | 'event' | 'custom';
|
|
114
|
+
/** Milliseconds from request start to span start. */
|
|
115
|
+
startOffset: number;
|
|
116
|
+
/** Span duration in milliseconds. */
|
|
117
|
+
duration: number;
|
|
118
|
+
/** Optional metadata (query bindings, status code, etc.). */
|
|
119
|
+
metadata?: Record<string, any>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* A complete trace for a single HTTP request.
|
|
123
|
+
*
|
|
124
|
+
* Contains all spans captured during the request lifecycle,
|
|
125
|
+
* stored in a {@link RingBuffer} by the {@link TraceCollector}.
|
|
126
|
+
*/
|
|
127
|
+
export interface TraceRecord {
|
|
128
|
+
/** Auto-incrementing sequence number. */
|
|
129
|
+
id: number;
|
|
130
|
+
/** HTTP method (e.g. `'GET'`, `'POST'`). */
|
|
131
|
+
method: string;
|
|
132
|
+
/** Request URL including query string. */
|
|
133
|
+
url: string;
|
|
134
|
+
/** HTTP response status code. */
|
|
135
|
+
statusCode: number;
|
|
136
|
+
/** Total request duration in milliseconds. */
|
|
137
|
+
totalDuration: number;
|
|
138
|
+
/** Number of spans captured. */
|
|
139
|
+
spanCount: number;
|
|
140
|
+
/** All spans captured during this request. */
|
|
141
|
+
spans: TraceSpan[];
|
|
142
|
+
/** Warnings captured via `console.warn` during this request. */
|
|
143
|
+
warnings: string[];
|
|
144
|
+
/** Unix timestamp in **milliseconds** when the request started. */
|
|
145
|
+
timestamp: number;
|
|
146
|
+
}
|
|
99
147
|
/**
|
|
100
148
|
* Resolved dev toolbar configuration with all defaults applied.
|
|
101
149
|
*
|
|
@@ -115,6 +163,10 @@ export interface DevToolbarConfig {
|
|
|
115
163
|
slowQueryThresholdMs: number;
|
|
116
164
|
/** Whether/where to persist debug data to disk across restarts. */
|
|
117
165
|
persistDebugData: boolean | string;
|
|
166
|
+
/** Whether per-request tracing is enabled. */
|
|
167
|
+
tracing: boolean;
|
|
168
|
+
/** Maximum traces to keep in the ring buffer. */
|
|
169
|
+
maxTraces: number;
|
|
118
170
|
}
|
|
119
171
|
/**
|
|
120
172
|
* Color names available for the `badge` column format.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/debug/types.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAA;IAEV,2DAA2D;IAC3D,GAAG,EAAE,MAAM,CAAA;IAEX,yDAAyD;IACzD,QAAQ,EAAE,GAAG,EAAE,CAAA;IAEf,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAA;IAEd,wEAAwE;IACxE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAEpB,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAA;IAElB,mDAAmD;IACnD,aAAa,EAAE,OAAO,CAAA;IAEtB,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAA;IAEV,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IAEb,oEAAoE;IACpE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAEnB,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAA;IAEV,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAA;IAEZ,2CAA2C;IAC3C,EAAE,EAAE,MAAM,CAAA;IAEV,wCAAwC;IACxC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IAEjB,yCAAyC;IACzC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAElB,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAA;IAEf,oDAAoD;IACpD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAEnB,kCAAkC;IAClC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAEnB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAA;IAEd,+BAA+B;IAC/B,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAA;IAEhD,8DAA8D;IAC9D,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IAExB,kCAAkC;IAClC,eAAe,EAAE,MAAM,CAAA;IAEvB,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAA;IAEd,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAA;IAEf,oDAAoD;IACpD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAEnB,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAA;IAEf;;;;OAIG;IACH,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAMD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,0CAA0C;IAC1C,OAAO,EAAE,OAAO,CAAA;IAEhB,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAA;IAElB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAA;IAEjB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAA;IAEjB,0DAA0D;IAC1D,oBAAoB,EAAE,MAAM,CAAA;IAE5B,mEAAmE;IACnE,gBAAgB,EAAE,OAAO,GAAG,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/debug/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
position: fixed;
|
|
7
7
|
inset-inline: 0;
|
|
8
8
|
bottom: 28px;
|
|
9
|
-
z-index:
|
|
9
|
+
z-index: 147;
|
|
10
10
|
height: 50vh;
|
|
11
11
|
display: none;
|
|
12
12
|
flex-direction: column;
|
|
@@ -317,7 +317,7 @@
|
|
|
317
317
|
display: flex;
|
|
318
318
|
flex-direction: column;
|
|
319
319
|
background: #0f0f0f;
|
|
320
|
-
z-index:
|
|
320
|
+
z-index: 30;
|
|
321
321
|
}
|
|
322
322
|
.ss-dbg-email-preview-header {
|
|
323
323
|
display: flex;
|
|
@@ -363,6 +363,124 @@
|
|
|
363
363
|
/* Filterable cell */
|
|
364
364
|
.ss-dbg-filterable:hover { background: rgba(52, 211, 153, 0.08); }
|
|
365
365
|
|
|
366
|
+
/* Timeline */
|
|
367
|
+
.ss-dbg-tl-detail-header {
|
|
368
|
+
display: flex;
|
|
369
|
+
align-items: center;
|
|
370
|
+
gap: 12px;
|
|
371
|
+
padding: 8px 12px;
|
|
372
|
+
border-bottom: 1px solid #262626;
|
|
373
|
+
background: #141414;
|
|
374
|
+
flex-shrink: 0;
|
|
375
|
+
}
|
|
376
|
+
#ss-dbg-tl-waterfall {
|
|
377
|
+
padding: 8px 12px;
|
|
378
|
+
overflow: auto;
|
|
379
|
+
scrollbar-width: none;
|
|
380
|
+
-ms-overflow-style: none;
|
|
381
|
+
}
|
|
382
|
+
#ss-dbg-tl-waterfall::-webkit-scrollbar { display: none; }
|
|
383
|
+
|
|
384
|
+
.ss-dbg-tl-row {
|
|
385
|
+
display: flex;
|
|
386
|
+
align-items: center;
|
|
387
|
+
height: 24px;
|
|
388
|
+
font-size: 11px;
|
|
389
|
+
border-bottom: 1px solid #1a1a1a;
|
|
390
|
+
}
|
|
391
|
+
.ss-dbg-tl-row:hover { background: rgba(38, 38, 38, 0.4); }
|
|
392
|
+
.ss-dbg-tl-label {
|
|
393
|
+
width: 280px;
|
|
394
|
+
min-width: 280px;
|
|
395
|
+
padding-right: 8px;
|
|
396
|
+
overflow: hidden;
|
|
397
|
+
text-overflow: ellipsis;
|
|
398
|
+
white-space: nowrap;
|
|
399
|
+
color: #a3a3a3;
|
|
400
|
+
font-size: 10px;
|
|
401
|
+
}
|
|
402
|
+
.ss-dbg-tl-track {
|
|
403
|
+
flex: 1;
|
|
404
|
+
position: relative;
|
|
405
|
+
height: 16px;
|
|
406
|
+
}
|
|
407
|
+
.ss-dbg-tl-bar {
|
|
408
|
+
position: absolute;
|
|
409
|
+
height: 12px;
|
|
410
|
+
top: 2px;
|
|
411
|
+
border-radius: 2px;
|
|
412
|
+
min-width: 2px;
|
|
413
|
+
cursor: default;
|
|
414
|
+
}
|
|
415
|
+
.ss-dbg-tl-bar-request { background: #1e3a5f; }
|
|
416
|
+
.ss-dbg-tl-bar-middleware { background: #1e3a5f; opacity: 0.7; }
|
|
417
|
+
.ss-dbg-tl-bar-db { background: #6d28d9; }
|
|
418
|
+
.ss-dbg-tl-bar-view { background: #0e7490; }
|
|
419
|
+
.ss-dbg-tl-bar-mail { background: #059669; }
|
|
420
|
+
.ss-dbg-tl-bar-event { background: #b45309; }
|
|
421
|
+
.ss-dbg-tl-bar-custom { background: #525252; }
|
|
422
|
+
|
|
423
|
+
.ss-dbg-tl-dur {
|
|
424
|
+
font-size: 10px;
|
|
425
|
+
color: #525252;
|
|
426
|
+
margin-left: 4px;
|
|
427
|
+
white-space: nowrap;
|
|
428
|
+
font-variant-numeric: tabular-nums;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.ss-dbg-tl-legend {
|
|
432
|
+
display: flex;
|
|
433
|
+
gap: 12px;
|
|
434
|
+
padding: 6px 12px;
|
|
435
|
+
border-bottom: 1px solid #262626;
|
|
436
|
+
background: #141414;
|
|
437
|
+
font-size: 10px;
|
|
438
|
+
color: #737373;
|
|
439
|
+
}
|
|
440
|
+
.ss-dbg-tl-legend-item {
|
|
441
|
+
display: flex;
|
|
442
|
+
align-items: center;
|
|
443
|
+
gap: 4px;
|
|
444
|
+
}
|
|
445
|
+
.ss-dbg-tl-legend-dot {
|
|
446
|
+
width: 8px;
|
|
447
|
+
height: 8px;
|
|
448
|
+
border-radius: 2px;
|
|
449
|
+
flex-shrink: 0;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.ss-dbg-tl-warnings {
|
|
453
|
+
margin-top: 12px;
|
|
454
|
+
padding: 8px 12px;
|
|
455
|
+
border-top: 1px solid #262626;
|
|
456
|
+
}
|
|
457
|
+
.ss-dbg-tl-warnings-title {
|
|
458
|
+
font-size: 10px;
|
|
459
|
+
font-weight: 600;
|
|
460
|
+
color: #fbbf24;
|
|
461
|
+
text-transform: uppercase;
|
|
462
|
+
letter-spacing: 0.05em;
|
|
463
|
+
margin-bottom: 4px;
|
|
464
|
+
}
|
|
465
|
+
.ss-dbg-tl-warning {
|
|
466
|
+
font-size: 11px;
|
|
467
|
+
color: #fbbf24;
|
|
468
|
+
padding: 2px 0;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.ss-dbg-tl-meta {
|
|
472
|
+
font-size: 10px;
|
|
473
|
+
color: #525252;
|
|
474
|
+
margin-left: 8px;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/* Status code badges */
|
|
478
|
+
.ss-dbg-status { font-size: 10px; font-weight: 600; padding: 1px 5px; border-radius: 3px; }
|
|
479
|
+
.ss-dbg-status-2xx { background: #064e3b; color: #34d399; }
|
|
480
|
+
.ss-dbg-status-3xx { background: #1e3a5f; color: #60a5fa; }
|
|
481
|
+
.ss-dbg-status-4xx { background: #422006; color: #fbbf24; }
|
|
482
|
+
.ss-dbg-status-5xx { background: #450a0a; color: #f87171; }
|
|
483
|
+
|
|
366
484
|
/* Wrench button in stats bar */
|
|
367
485
|
.ss-dbg-btn {
|
|
368
486
|
display: flex;
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
|
|
19
19
|
const LOGS_ENDPOINT = panel.dataset.logsEndpoint || (BASE + '/logs');
|
|
20
20
|
|
|
21
|
+
const tracingEnabled = panel.dataset.tracing === '1';
|
|
22
|
+
|
|
21
23
|
let isOpen = false;
|
|
22
|
-
let activeTab = 'queries';
|
|
24
|
+
let activeTab = tracingEnabled ? 'timeline' : 'queries';
|
|
23
25
|
const fetched = {};
|
|
24
26
|
let refreshTimer = null;
|
|
25
27
|
let logFilter = 'all';
|
|
@@ -185,7 +187,8 @@
|
|
|
185
187
|
|
|
186
188
|
// ── Data loading ────────────────────────────────────────────────
|
|
187
189
|
const loadTab = (name) => {
|
|
188
|
-
if (name === '
|
|
190
|
+
if (name === 'timeline') fetchTraces();
|
|
191
|
+
else if (name === 'queries') fetchQueries();
|
|
189
192
|
else if (name === 'events') fetchEvents();
|
|
190
193
|
else if (name === 'routes' && !fetched.routes) fetchRoutes();
|
|
191
194
|
else if (name === 'logs') fetchLogs();
|
|
@@ -697,6 +700,194 @@
|
|
|
697
700
|
});
|
|
698
701
|
}
|
|
699
702
|
|
|
703
|
+
// ── Timeline Tab ────────────────────────────────────────────────
|
|
704
|
+
const tlSearchInput = document.getElementById('ss-dbg-search-timeline');
|
|
705
|
+
const tlSummaryEl = document.getElementById('ss-dbg-timeline-summary');
|
|
706
|
+
const tlBodyEl = document.getElementById('ss-dbg-timeline-body');
|
|
707
|
+
const tlListEl = document.getElementById('ss-dbg-timeline-list');
|
|
708
|
+
const tlDetailEl = document.getElementById('ss-dbg-timeline-detail');
|
|
709
|
+
const tlBackBtn = document.getElementById('ss-dbg-tl-back');
|
|
710
|
+
const tlDetailTitle = document.getElementById('ss-dbg-tl-detail-title');
|
|
711
|
+
const tlWaterfall = document.getElementById('ss-dbg-tl-waterfall');
|
|
712
|
+
let cachedTraces = { traces: [], total: 0 };
|
|
713
|
+
|
|
714
|
+
const statusClass = (code) => {
|
|
715
|
+
if (code >= 500) return 'ss-dbg-status-5xx';
|
|
716
|
+
if (code >= 400) return 'ss-dbg-status-4xx';
|
|
717
|
+
if (code >= 300) return 'ss-dbg-status-3xx';
|
|
718
|
+
return 'ss-dbg-status-2xx';
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
const fetchTraces = () => {
|
|
722
|
+
if (!tracingEnabled) return;
|
|
723
|
+
fetchJSON(BASE + '/traces')
|
|
724
|
+
.then((data) => {
|
|
725
|
+
cachedTraces = data;
|
|
726
|
+
renderTraces();
|
|
727
|
+
})
|
|
728
|
+
.catch(() => {
|
|
729
|
+
if (tlBodyEl) tlBodyEl.innerHTML = '<div class="ss-dbg-empty">Failed to load traces</div>';
|
|
730
|
+
});
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
const renderTraces = () => {
|
|
734
|
+
if (!tlBodyEl) return;
|
|
735
|
+
const filter = (tlSearchInput ? tlSearchInput.value : '').toLowerCase();
|
|
736
|
+
const traces = cachedTraces.traces || [];
|
|
737
|
+
|
|
738
|
+
if (tlSummaryEl) {
|
|
739
|
+
tlSummaryEl.textContent = cachedTraces.total + ' requests';
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
let filtered = traces;
|
|
743
|
+
if (filter) {
|
|
744
|
+
filtered = traces.filter((t) =>
|
|
745
|
+
t.url.toLowerCase().indexOf(filter) !== -1
|
|
746
|
+
|| t.method.toLowerCase().indexOf(filter) !== -1
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (filtered.length === 0) {
|
|
751
|
+
tlBodyEl.innerHTML = '<div class="ss-dbg-empty">' + (filter ? 'No matching requests' : 'No requests traced yet') + '</div>';
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
let html = '<table class="ss-dbg-table"><thead><tr>'
|
|
756
|
+
+ '<th style="width:40px">#</th>'
|
|
757
|
+
+ '<th style="width:60px">Method</th>'
|
|
758
|
+
+ '<th>URL</th>'
|
|
759
|
+
+ '<th style="width:55px">Status</th>'
|
|
760
|
+
+ '<th style="width:70px">Duration</th>'
|
|
761
|
+
+ '<th style="width:50px">Spans</th>'
|
|
762
|
+
+ '<th style="width:30px" title="Warnings">⚠</th>'
|
|
763
|
+
+ '<th style="width:70px">Time</th>'
|
|
764
|
+
+ '</tr></thead><tbody>';
|
|
765
|
+
|
|
766
|
+
for (let i = 0; i < filtered.length; i++) {
|
|
767
|
+
const t = filtered[i];
|
|
768
|
+
html += '<tr class="ss-dbg-email-row" data-trace-id="' + t.id + '">'
|
|
769
|
+
+ '<td style="color:#525252">' + t.id + '</td>'
|
|
770
|
+
+ '<td><span class="' + methodClass(t.method) + '">' + esc(t.method) + '</span></td>'
|
|
771
|
+
+ '<td style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:300px" title="' + esc(t.url) + '">' + esc(t.url) + '</td>'
|
|
772
|
+
+ '<td><span class="ss-dbg-status ' + statusClass(t.statusCode) + '">' + t.statusCode + '</span></td>'
|
|
773
|
+
+ '<td class="ss-dbg-duration ' + durationClass(t.totalDuration) + '">' + t.totalDuration.toFixed(1) + 'ms</td>'
|
|
774
|
+
+ '<td style="color:#737373;text-align:center">' + t.spanCount + '</td>'
|
|
775
|
+
+ '<td style="text-align:center">' + (t.warningCount > 0 ? '<span style="color:#fbbf24">' + t.warningCount + '</span>' : '<span style="color:#333">-</span>') + '</td>'
|
|
776
|
+
+ '<td class="ss-dbg-event-time">' + timeAgo(t.timestamp) + '</td>'
|
|
777
|
+
+ '</tr>';
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
html += '</tbody></table>';
|
|
781
|
+
tlBodyEl.innerHTML = html;
|
|
782
|
+
|
|
783
|
+
// Click row to open detail
|
|
784
|
+
tlBodyEl.querySelectorAll('[data-trace-id]').forEach((row) => {
|
|
785
|
+
row.addEventListener('click', () => {
|
|
786
|
+
const id = row.getAttribute('data-trace-id');
|
|
787
|
+
fetchTraceDetail(id);
|
|
788
|
+
});
|
|
789
|
+
});
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
const fetchTraceDetail = (id) => {
|
|
793
|
+
fetchJSON(BASE + '/traces/' + id)
|
|
794
|
+
.then((trace) => {
|
|
795
|
+
showTimeline(trace);
|
|
796
|
+
})
|
|
797
|
+
.catch(() => {
|
|
798
|
+
if (tlWaterfall) tlWaterfall.innerHTML = '<div class="ss-dbg-empty">Failed to load trace</div>';
|
|
799
|
+
});
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
const showTimeline = (trace) => {
|
|
803
|
+
if (!tlListEl || !tlDetailEl || !tlDetailTitle || !tlWaterfall) return;
|
|
804
|
+
|
|
805
|
+
tlListEl.style.display = 'none';
|
|
806
|
+
tlDetailEl.style.display = '';
|
|
807
|
+
|
|
808
|
+
tlDetailTitle.innerHTML =
|
|
809
|
+
'<span class="' + methodClass(trace.method) + '">' + esc(trace.method) + '</span> '
|
|
810
|
+
+ esc(trace.url) + ' '
|
|
811
|
+
+ '<span class="ss-dbg-status ' + statusClass(trace.statusCode) + '">' + trace.statusCode + '</span>'
|
|
812
|
+
+ '<span class="ss-dbg-tl-meta">' + trace.totalDuration.toFixed(1) + 'ms · '
|
|
813
|
+
+ trace.spanCount + ' spans · '
|
|
814
|
+
+ formatTime(trace.timestamp) + '</span>';
|
|
815
|
+
|
|
816
|
+
const spans = trace.spans || [];
|
|
817
|
+
const total = trace.totalDuration || 1;
|
|
818
|
+
|
|
819
|
+
// Legend
|
|
820
|
+
let html = '<div class="ss-dbg-tl-legend">'
|
|
821
|
+
+ '<div class="ss-dbg-tl-legend-item"><span class="ss-dbg-tl-legend-dot" style="background:#6d28d9"></span>DB</div>'
|
|
822
|
+
+ '<div class="ss-dbg-tl-legend-item"><span class="ss-dbg-tl-legend-dot" style="background:#1e3a5f"></span>Request</div>'
|
|
823
|
+
+ '<div class="ss-dbg-tl-legend-item"><span class="ss-dbg-tl-legend-dot" style="background:#059669"></span>Mail</div>'
|
|
824
|
+
+ '<div class="ss-dbg-tl-legend-item"><span class="ss-dbg-tl-legend-dot" style="background:#b45309"></span>Event</div>'
|
|
825
|
+
+ '<div class="ss-dbg-tl-legend-item"><span class="ss-dbg-tl-legend-dot" style="background:#0e7490"></span>View</div>'
|
|
826
|
+
+ '<div class="ss-dbg-tl-legend-item"><span class="ss-dbg-tl-legend-dot" style="background:#525252"></span>Custom</div>'
|
|
827
|
+
+ '</div>';
|
|
828
|
+
|
|
829
|
+
if (spans.length === 0) {
|
|
830
|
+
html += '<div class="ss-dbg-empty">No spans captured for this request</div>';
|
|
831
|
+
} else {
|
|
832
|
+
// Build nesting depth from parentId
|
|
833
|
+
const depthMap = {};
|
|
834
|
+
for (let i = 0; i < spans.length; i++) {
|
|
835
|
+
const s = spans[i];
|
|
836
|
+
if (!s.parentId) {
|
|
837
|
+
depthMap[s.id] = 0;
|
|
838
|
+
} else {
|
|
839
|
+
depthMap[s.id] = (depthMap[s.parentId] || 0) + 1;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Sort by startOffset
|
|
844
|
+
const sorted = spans.slice().sort((a, b) => a.startOffset - b.startOffset);
|
|
845
|
+
|
|
846
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
847
|
+
const s = sorted[i];
|
|
848
|
+
const depth = depthMap[s.id] || 0;
|
|
849
|
+
const leftPct = (s.startOffset / total * 100).toFixed(2);
|
|
850
|
+
const widthPct = Math.max(s.duration / total * 100, 0.5).toFixed(2);
|
|
851
|
+
const indent = depth * 16;
|
|
852
|
+
const catLabel = s.category === 'db' ? 'DB' : s.category;
|
|
853
|
+
const metaStr = s.metadata ? Object.entries(s.metadata).filter(([,v]) => v != null).map(([k,v]) => k + '=' + v).join(', ') : '';
|
|
854
|
+
const tooltip = s.label + ' (' + s.duration.toFixed(2) + 'ms)' + (metaStr ? '\n' + metaStr : '');
|
|
855
|
+
|
|
856
|
+
html += '<div class="ss-dbg-tl-row">'
|
|
857
|
+
+ '<div class="ss-dbg-tl-label" style="padding-left:' + (8 + indent) + 'px" title="' + esc(tooltip) + '">'
|
|
858
|
+
+ '<span class="ss-dbg-badge ss-dbg-badge-' + (s.category === 'db' ? 'purple' : s.category === 'mail' ? 'green' : s.category === 'event' ? 'amber' : s.category === 'view' ? 'blue' : 'muted') + '" style="font-size:9px;margin-right:4px">' + esc(catLabel) + '</span>'
|
|
859
|
+
+ esc(s.label.length > 40 ? s.label.slice(0, 40) + '...' : s.label)
|
|
860
|
+
+ '</div>'
|
|
861
|
+
+ '<div class="ss-dbg-tl-track">'
|
|
862
|
+
+ '<div class="ss-dbg-tl-bar ss-dbg-tl-bar-' + esc(s.category) + '" style="left:' + leftPct + '%;width:' + widthPct + '%" title="' + esc(tooltip) + '"></div>'
|
|
863
|
+
+ '</div>'
|
|
864
|
+
+ '<span class="ss-dbg-tl-dur">' + s.duration.toFixed(2) + 'ms</span>'
|
|
865
|
+
+ '</div>';
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Warnings
|
|
870
|
+
if (trace.warnings && trace.warnings.length > 0) {
|
|
871
|
+
html += '<div class="ss-dbg-tl-warnings">'
|
|
872
|
+
+ '<div class="ss-dbg-tl-warnings-title">Warnings (' + trace.warnings.length + ')</div>';
|
|
873
|
+
for (let w = 0; w < trace.warnings.length; w++) {
|
|
874
|
+
html += '<div class="ss-dbg-tl-warning">' + esc(trace.warnings[w]) + '</div>';
|
|
875
|
+
}
|
|
876
|
+
html += '</div>';
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
tlWaterfall.innerHTML = html;
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
if (tlBackBtn) {
|
|
883
|
+
tlBackBtn.addEventListener('click', () => {
|
|
884
|
+
if (tlListEl) tlListEl.style.display = '';
|
|
885
|
+
if (tlDetailEl) tlDetailEl.style.display = 'none';
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
if (tlSearchInput) tlSearchInput.addEventListener('input', renderTraces);
|
|
890
|
+
|
|
700
891
|
// ── Custom panes: fetch, render, bind ───────────────────────────
|
|
701
892
|
const getNestedValue = (obj, path) => {
|
|
702
893
|
const parts = path.split('.');
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
position: fixed;
|
|
7
7
|
inset-inline: 0;
|
|
8
8
|
bottom: 0;
|
|
9
|
-
z-index:
|
|
9
|
+
z-index: 150;
|
|
10
10
|
display: flex;
|
|
11
11
|
height: 28px;
|
|
12
12
|
align-items: center;
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
.ss-toggle {
|
|
103
103
|
position: fixed;
|
|
104
104
|
right: 12px;
|
|
105
|
-
z-index:
|
|
105
|
+
z-index: 150;
|
|
106
106
|
display: flex;
|
|
107
107
|
align-items: center;
|
|
108
108
|
gap: 6px;
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
/* Tooltip — positioned absolutely within .ss-bar (outside scroll area) via JS */
|
|
140
140
|
.ss-tooltip {
|
|
141
141
|
position: absolute;
|
|
142
|
-
z-index:
|
|
142
|
+
z-index: 180;
|
|
143
143
|
pointer-events: none;
|
|
144
144
|
}
|
|
145
145
|
.ss-tooltip.ss-pinned {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/edge/plugin.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAKrD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,IACrD,MAAM,GAAG,
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/edge/plugin.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAKrD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,IACrD,MAAM,GAAG,UAgIlB"}
|
package/dist/src/edge/plugin.js
CHANGED
|
@@ -98,6 +98,7 @@ export function edgePluginServerStats(config) {
|
|
|
98
98
|
state.debugJs = read("client/debug-panel.js");
|
|
99
99
|
state.logsEndpoint = "/admin/api/debug/logs";
|
|
100
100
|
state.customPanes = config.devToolbar?.panes || [];
|
|
101
|
+
state.showTracing = !!config.devToolbar?.tracing;
|
|
101
102
|
}
|
|
102
103
|
// Pre-render via Template directly — bypasses edge.createRenderer() which
|
|
103
104
|
// would re-run #executePlugins and cause infinite recursion.
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
<div id="ss-dbg-panel" class="ss-dbg-panel" data-logs-endpoint="{{ logsEndpoint }}">
|
|
1
|
+
<div id="ss-dbg-panel" class="ss-dbg-panel" data-logs-endpoint="{{ logsEndpoint }}" data-tracing="{{ showTracing ? '1' : '0' }}">
|
|
2
2
|
<div class="ss-dbg-tabs">
|
|
3
|
-
|
|
3
|
+
@if(showTracing)
|
|
4
|
+
<button type="button" class="ss-dbg-tab ss-dbg-active" data-ss-dbg-tab="timeline">Timeline</button>
|
|
5
|
+
<button type="button" class="ss-dbg-tab" data-ss-dbg-tab="queries">Queries</button>
|
|
6
|
+
@else
|
|
7
|
+
<button type="button" class="ss-dbg-tab ss-dbg-active" data-ss-dbg-tab="queries">Queries</button>
|
|
8
|
+
@end
|
|
4
9
|
<button type="button" class="ss-dbg-tab" data-ss-dbg-tab="events">Events</button>
|
|
5
10
|
<button type="button" class="ss-dbg-tab" data-ss-dbg-tab="routes">Routes</button>
|
|
6
11
|
<button type="button" class="ss-dbg-tab" data-ss-dbg-tab="logs">Logs</button>
|
|
@@ -14,7 +19,25 @@
|
|
|
14
19
|
</div>
|
|
15
20
|
</div>
|
|
16
21
|
<div class="ss-dbg-content">
|
|
17
|
-
|
|
22
|
+
@if(showTracing)
|
|
23
|
+
<div id="ss-dbg-pane-timeline" class="ss-dbg-pane ss-dbg-active">
|
|
24
|
+
<div id="ss-dbg-timeline-list">
|
|
25
|
+
<div class="ss-dbg-search-bar">
|
|
26
|
+
<input type="text" class="ss-dbg-search" id="ss-dbg-search-timeline" placeholder="Filter by URL or method..." />
|
|
27
|
+
<span id="ss-dbg-timeline-summary" class="ss-dbg-summary"></span>
|
|
28
|
+
</div>
|
|
29
|
+
<div id="ss-dbg-timeline-body"><div class="ss-dbg-empty">Loading traces...</div></div>
|
|
30
|
+
</div>
|
|
31
|
+
<div id="ss-dbg-timeline-detail" style="display:none">
|
|
32
|
+
<div class="ss-dbg-tl-detail-header">
|
|
33
|
+
<button type="button" class="ss-dbg-btn-clear" id="ss-dbg-tl-back">← Back</button>
|
|
34
|
+
<span id="ss-dbg-tl-detail-title" class="ss-dbg-summary"></span>
|
|
35
|
+
</div>
|
|
36
|
+
<div id="ss-dbg-tl-waterfall"></div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
@end
|
|
40
|
+
<div id="ss-dbg-pane-queries" class="ss-dbg-pane{{ showTracing ? '' : ' ss-dbg-active' }}">
|
|
18
41
|
<div class="ss-dbg-search-bar">
|
|
19
42
|
<input type="text" class="ss-dbg-search" id="ss-dbg-search-queries" placeholder="Filter queries by SQL, model, or method..." />
|
|
20
43
|
<span id="ss-dbg-queries-summary" class="ss-dbg-summary"></span>
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { defineConfig } from './define_config.js';
|
|
2
2
|
export { StatsEngine } from './engine/stats_engine.js';
|
|
3
3
|
export { RequestMetrics } from './engine/request_metrics.js';
|
|
4
|
+
export { trace } from './debug/trace_collector.js';
|
|
4
5
|
export type { MetricCollector } from './collectors/collector.js';
|
|
5
6
|
export type { MetricValue, ServerStats, ServerStatsConfig, LogStats, DevToolbarOptions } from './types.js';
|
|
6
|
-
export type { DebugPane, DebugPaneColumn, DebugPaneFormatType, DebugPaneSearch, BadgeColor, QueryRecord, EventRecord, EmailRecord, RouteRecord, DevToolbarConfig, } from './debug/types.js';
|
|
7
|
+
export type { DebugPane, DebugPaneColumn, DebugPaneFormatType, DebugPaneSearch, BadgeColor, QueryRecord, EventRecord, EmailRecord, RouteRecord, TraceSpan, TraceRecord, DevToolbarConfig, } from './debug/types.js';
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC1G,YAAY,EACV,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,UAAU,EACV,WAAW,EACX,WAAW,EACX,WAAW,EACX,WAAW,EACX,gBAAgB,GACjB,MAAM,kBAAkB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAA;AAClD,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC1G,YAAY,EACV,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,UAAU,EACV,WAAW,EACX,WAAW,EACX,WAAW,EACX,WAAW,EACX,SAAS,EACT,WAAW,EACX,gBAAgB,GACjB,MAAM,kBAAkB,CAAA"}
|
package/dist/src/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { HttpContext } from "@adonisjs/core/http";
|
|
2
2
|
import type { NextFn } from "@adonisjs/core/types/http";
|
|
3
|
+
import type { TraceCollector } from "../debug/trace_collector.js";
|
|
3
4
|
export declare function setShouldShow(fn: ((ctx: any) => boolean) | null): void;
|
|
5
|
+
export declare function setTraceCollector(collector: TraceCollector | null): void;
|
|
4
6
|
export default class RequestTrackingMiddleware {
|
|
5
7
|
handle(ctx: HttpContext, next: NextFn): Promise<void>;
|
|
6
8
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request_tracking_middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/request_tracking_middleware.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"request_tracking_middleware.d.ts","sourceRoot":"","sources":["../../../src/middleware/request_tracking_middleware.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAOlE,wBAAgB,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,IAAI,QAE/D;AAOD,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,QAEjE;AAED,MAAM,CAAC,OAAO,OAAO,yBAAyB;IACtC,MAAM,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM;CA4C5C"}
|
|
@@ -7,6 +7,13 @@ let shouldShowFn = null;
|
|
|
7
7
|
export function setShouldShow(fn) {
|
|
8
8
|
shouldShowFn = fn;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Module-level trace collector, set by the provider when tracing is enabled.
|
|
12
|
+
*/
|
|
13
|
+
let traceCollector = null;
|
|
14
|
+
export function setTraceCollector(collector) {
|
|
15
|
+
traceCollector = collector;
|
|
16
|
+
}
|
|
10
17
|
export default class RequestTrackingMiddleware {
|
|
11
18
|
async handle(ctx, next) {
|
|
12
19
|
const metrics = getRequestMetrics();
|
|
@@ -29,13 +36,22 @@ export default class RequestTrackingMiddleware {
|
|
|
29
36
|
},
|
|
30
37
|
});
|
|
31
38
|
}
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
const runRequest = async () => {
|
|
40
|
+
try {
|
|
41
|
+
await next();
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
const duration = performance.now() - start;
|
|
45
|
+
metrics.decrementActiveConnections();
|
|
46
|
+
metrics.recordRequest(duration, ctx.response.getStatus());
|
|
47
|
+
traceCollector?.finishTrace(ctx.request.method(), ctx.request.url(true), ctx.response.getStatus());
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
if (traceCollector) {
|
|
51
|
+
await traceCollector.startTrace(runRequest);
|
|
34
52
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
metrics.decrementActiveConnections();
|
|
38
|
-
metrics.recordRequest(duration, ctx.response.getStatus());
|
|
53
|
+
else {
|
|
54
|
+
await runRequest();
|
|
39
55
|
}
|
|
40
56
|
}
|
|
41
57
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server_stats_provider.d.ts","sourceRoot":"","sources":["../../../src/provider/server_stats_provider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAI/D,MAAM,CAAC,OAAO,OAAO,mBAAmB;IAO1B,SAAS,CAAC,GAAG,EAAE,kBAAkB;IAN7C,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,UAAU,CAA+C;gBAE3C,GAAG,EAAE,kBAAkB;IAEvC,IAAI;IAoBJ,KAAK;
|
|
1
|
+
{"version":3,"file":"server_stats_provider.d.ts","sourceRoot":"","sources":["../../../src/provider/server_stats_provider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAI/D,MAAM,CAAC,OAAO,OAAO,mBAAmB;IAO1B,SAAS,CAAC,GAAG,EAAE,kBAAkB;IAN7C,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,UAAU,CAA+C;gBAE3C,GAAG,EAAE,kBAAkB;IAEvC,IAAI;IAoBJ,KAAK;YAsEG,eAAe;IAoDvB,QAAQ;CAuBf"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StatsEngine } from "../engine/stats_engine.js";
|
|
2
2
|
import { DebugStore } from "../debug/debug_store.js";
|
|
3
|
-
import { setShouldShow } from "../middleware/request_tracking_middleware.js";
|
|
3
|
+
import { setShouldShow, setTraceCollector } from "../middleware/request_tracking_middleware.js";
|
|
4
4
|
export default class ServerStatsProvider {
|
|
5
5
|
app;
|
|
6
6
|
intervalId = null;
|
|
@@ -50,6 +50,8 @@ export default class ServerStatsProvider {
|
|
|
50
50
|
maxEmails: toolbarConfig.maxEmails ?? 100,
|
|
51
51
|
slowQueryThresholdMs: toolbarConfig.slowQueryThresholdMs ?? 100,
|
|
52
52
|
persistDebugData: toolbarConfig.persistDebugData ?? false,
|
|
53
|
+
tracing: toolbarConfig.tracing ?? false,
|
|
54
|
+
maxTraces: toolbarConfig.maxTraces ?? 200,
|
|
53
55
|
});
|
|
54
56
|
}
|
|
55
57
|
let transmit = null;
|
|
@@ -113,6 +115,10 @@ export default class ServerStatsProvider {
|
|
|
113
115
|
// Router not available
|
|
114
116
|
}
|
|
115
117
|
await this.debugStore.start(emitter, router);
|
|
118
|
+
// Wire trace collector into the request tracking middleware
|
|
119
|
+
if (this.debugStore.traces) {
|
|
120
|
+
setTraceCollector(this.debugStore.traces);
|
|
121
|
+
}
|
|
116
122
|
// Periodic flush every 30 seconds (handles crashes)
|
|
117
123
|
if (this.persistPath) {
|
|
118
124
|
this.flushTimer = setInterval(async () => {
|
package/dist/src/types.d.ts
CHANGED
|
@@ -230,6 +230,21 @@ export interface DevToolbarOptions {
|
|
|
230
230
|
* @default false
|
|
231
231
|
*/
|
|
232
232
|
persistDebugData?: boolean | string;
|
|
233
|
+
/**
|
|
234
|
+
* Enable per-request tracing with timeline visualization.
|
|
235
|
+
*
|
|
236
|
+
* When enabled, each HTTP request is traced with a waterfall
|
|
237
|
+
* of all operations (DB queries, events, mail) that occurred
|
|
238
|
+
* during the request.
|
|
239
|
+
*
|
|
240
|
+
* @default false
|
|
241
|
+
*/
|
|
242
|
+
tracing?: boolean;
|
|
243
|
+
/**
|
|
244
|
+
* Maximum number of request traces to keep in the ring buffer.
|
|
245
|
+
* @default 200
|
|
246
|
+
*/
|
|
247
|
+
maxTraces?: number;
|
|
233
248
|
}
|
|
234
249
|
/**
|
|
235
250
|
* Top-level configuration for `adonisjs-server-stats`.
|
package/dist/src/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|