adonisjs-server-stats 1.4.0 → 1.5.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.
Files changed (84) hide show
  1. package/README.md +272 -142
  2. package/dist/configure.d.ts.map +1 -1
  3. package/dist/src/controller/debug_controller.d.ts +2 -2
  4. package/dist/src/controller/debug_controller.d.ts.map +1 -1
  5. package/dist/src/controller/server_stats_controller.d.ts +1 -1
  6. package/dist/src/controller/server_stats_controller.d.ts.map +1 -1
  7. package/dist/src/dashboard/chart_aggregator.d.ts.map +1 -1
  8. package/dist/src/dashboard/chart_aggregator.js +8 -8
  9. package/dist/src/dashboard/dashboard_controller.d.ts +12 -97
  10. package/dist/src/dashboard/dashboard_controller.d.ts.map +1 -1
  11. package/dist/src/dashboard/dashboard_controller.js +244 -522
  12. package/dist/src/dashboard/dashboard_routes.d.ts.map +1 -1
  13. package/dist/src/dashboard/dashboard_routes.js +7 -2
  14. package/dist/src/dashboard/dashboard_store.d.ts +6 -3
  15. package/dist/src/dashboard/dashboard_store.d.ts.map +1 -1
  16. package/dist/src/dashboard/dashboard_store.js +54 -78
  17. package/dist/src/dashboard/integrations/cache_inspector.d.ts.map +1 -1
  18. package/dist/src/dashboard/integrations/queue_inspector.d.ts.map +1 -1
  19. package/dist/src/dashboard/migrator.d.ts.map +1 -1
  20. package/dist/src/dashboard/migrator.js +3 -1
  21. package/dist/src/dashboard/models/stats_event.d.ts +1 -1
  22. package/dist/src/dashboard/models/stats_event.d.ts.map +1 -1
  23. package/dist/src/dashboard/models/stats_query.d.ts +1 -1
  24. package/dist/src/dashboard/models/stats_query.d.ts.map +1 -1
  25. package/dist/src/dashboard/models/stats_request.d.ts +2 -2
  26. package/dist/src/dashboard/models/stats_request.d.ts.map +1 -1
  27. package/dist/src/dashboard/models/stats_request.js +1 -1
  28. package/dist/src/dashboard/models/stats_trace.d.ts +1 -1
  29. package/dist/src/dashboard/models/stats_trace.d.ts.map +1 -1
  30. package/dist/src/debug/debug_store.d.ts +6 -6
  31. package/dist/src/debug/debug_store.d.ts.map +1 -1
  32. package/dist/src/debug/debug_store.js +10 -10
  33. package/dist/src/debug/email_collector.d.ts +0 -9
  34. package/dist/src/debug/email_collector.d.ts.map +1 -1
  35. package/dist/src/debug/email_collector.js +6 -28
  36. package/dist/src/debug/event_collector.d.ts +1 -1
  37. package/dist/src/debug/event_collector.d.ts.map +1 -1
  38. package/dist/src/debug/event_collector.js +17 -17
  39. package/dist/src/debug/query_collector.d.ts +1 -1
  40. package/dist/src/debug/query_collector.d.ts.map +1 -1
  41. package/dist/src/debug/query_collector.js +13 -14
  42. package/dist/src/debug/ring_buffer.d.ts.map +1 -1
  43. package/dist/src/debug/route_inspector.d.ts +1 -1
  44. package/dist/src/debug/route_inspector.d.ts.map +1 -1
  45. package/dist/src/debug/route_inspector.js +12 -12
  46. package/dist/src/debug/trace_collector.d.ts.map +1 -1
  47. package/dist/src/debug/trace_collector.js +6 -5
  48. package/dist/src/edge/client/dashboard.css +516 -171
  49. package/dist/src/edge/client/dashboard.js +2756 -1662
  50. package/dist/src/edge/client/debug-panel.css +476 -133
  51. package/dist/src/edge/client/debug-panel.js +1496 -1043
  52. package/dist/src/edge/client/stats-bar.css +64 -30
  53. package/dist/src/edge/client/stats-bar.js +598 -319
  54. package/dist/src/edge/plugin.d.ts +1 -1
  55. package/dist/src/edge/plugin.d.ts.map +1 -1
  56. package/dist/src/edge/plugin.js +41 -59
  57. package/dist/src/edge/views/stats-bar.edge +1 -1
  58. package/dist/src/index.d.ts +1 -1
  59. package/dist/src/index.d.ts.map +1 -1
  60. package/dist/src/middleware/request_tracking_middleware.d.ts +4 -4
  61. package/dist/src/middleware/request_tracking_middleware.d.ts.map +1 -1
  62. package/dist/src/middleware/request_tracking_middleware.js +7 -6
  63. package/dist/src/prometheus/prometheus_collector.d.ts +1 -1
  64. package/dist/src/prometheus/prometheus_collector.d.ts.map +1 -1
  65. package/dist/src/provider/server_stats_provider.d.ts +1 -1
  66. package/dist/src/provider/server_stats_provider.d.ts.map +1 -1
  67. package/dist/src/provider/server_stats_provider.js +33 -32
  68. package/dist/src/types.d.ts +2 -2
  69. package/dist/src/utils/json_helpers.d.ts +8 -0
  70. package/dist/src/utils/json_helpers.d.ts.map +1 -0
  71. package/dist/src/utils/json_helpers.js +21 -0
  72. package/dist/src/utils/mail_helpers.d.ts +13 -0
  73. package/dist/src/utils/mail_helpers.d.ts.map +1 -0
  74. package/dist/src/utils/mail_helpers.js +26 -0
  75. package/dist/src/utils/math_helpers.d.ts +8 -0
  76. package/dist/src/utils/math_helpers.d.ts.map +1 -0
  77. package/dist/src/utils/math_helpers.js +11 -0
  78. package/dist/src/utils/time_helpers.d.ts +12 -0
  79. package/dist/src/utils/time_helpers.d.ts.map +1 -0
  80. package/dist/src/utils/time_helpers.js +32 -0
  81. package/dist/src/utils/transmit_client.d.ts +9 -0
  82. package/dist/src/utils/transmit_client.d.ts.map +1 -0
  83. package/dist/src/utils/transmit_client.js +20 -0
  84. package/package.json +35 -29
package/README.md CHANGED
@@ -19,16 +19,16 @@ Zero frontend dependencies. Zero build step. Just `@serverStats()` and go.
19
19
 
20
20
  **Debug toolbar** -- expandable panels for deep inspection:
21
21
 
22
- | Queries | Events |
23
- |---------|--------|
22
+ | Queries | Events |
23
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
24
24
  | ![Queries panel showing SQL queries with duration and model info](https://raw.githubusercontent.com/simulieren/adonisjs-server-stats/main/screenshots/debug-queries.png) | ![Events panel showing application events with payload data](https://raw.githubusercontent.com/simulieren/adonisjs-server-stats/main/screenshots/debug-events.png) |
25
25
 
26
- | Routes | Logs |
27
- |--------|------|
26
+ | Routes | Logs |
27
+ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
28
28
  | ![Routes panel showing all registered routes with handlers](https://raw.githubusercontent.com/simulieren/adonisjs-server-stats/main/screenshots/debug-routes.png) | ![Logs panel with level filtering and request ID correlation](https://raw.githubusercontent.com/simulieren/adonisjs-server-stats/main/screenshots/debug-logs.png) |
29
29
 
30
- | Emails (custom pane) |
31
- |----------------------|
30
+ | Emails (custom pane) |
31
+ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
32
32
  | ![Emails panel showing sent emails with delivery status](https://raw.githubusercontent.com/simulieren/adonisjs-server-stats/main/screenshots/debug-emails.png) |
33
33
 
34
34
  ## Features
@@ -74,9 +74,7 @@ providers: [
74
74
 
75
75
  ```ts
76
76
  // start/kernel.ts
77
- server.use([
78
- () => import('adonisjs-server-stats/middleware'),
79
- ])
77
+ server.use([() => import('adonisjs-server-stats/middleware')])
80
78
  ```
81
79
 
82
80
  ### 3. Create config
@@ -87,11 +85,7 @@ import { defineConfig } from 'adonisjs-server-stats'
87
85
  import { processCollector, systemCollector, httpCollector } from 'adonisjs-server-stats/collectors'
88
86
 
89
87
  export default defineConfig({
90
- collectors: [
91
- processCollector(),
92
- systemCollector(),
93
- httpCollector(),
94
- ],
88
+ collectors: [processCollector(), systemCollector(), httpCollector()],
95
89
  })
96
90
  ```
97
91
 
@@ -165,16 +159,13 @@ export default defineConfig({
165
159
  })
166
160
  ```
167
161
 
168
- ### 4. Add a route
162
+ ### 4. Add routes
169
163
 
170
- ```ts
171
- // start/routes.ts
172
- router
173
- .get('/admin/api/server-stats', '#controllers/admin/server_stats_controller.index')
174
- .use(middleware.superadmin()) // Replace with your own middleware
175
- ```
164
+ The package has three layers of functionality, each with its own routes:
165
+
166
+ #### Stats bar API route (required)
176
167
 
177
- ### 5. Create the controller
168
+ The stats bar polls this endpoint for live metrics. Create a controller and route:
178
169
 
179
170
  ```ts
180
171
  // app/controllers/admin/server_stats_controller.ts
@@ -184,13 +175,154 @@ import type { StatsEngine } from 'adonisjs-server-stats'
184
175
 
185
176
  export default class ServerStatsController {
186
177
  async index({ response }: HttpContext) {
187
- const engine = await app.container.make('server_stats.engine') as StatsEngine
178
+ const engine = (await app.container.make('server_stats.engine')) as StatsEngine
188
179
  return response.json(engine.getLatestStats())
189
180
  }
190
181
  }
191
182
  ```
192
183
 
193
- ### 6. Render the stats bar
184
+ ```ts
185
+ // start/routes.ts
186
+ router
187
+ .get('/admin/api/server-stats', '#controllers/admin/server_stats_controller.index')
188
+ .use(middleware.superadmin())
189
+ ```
190
+
191
+ > The route path must match `endpoint` in your config (default: `/admin/api/server-stats`).
192
+
193
+ #### Debug toolbar routes (optional -- when `devToolbar.enabled: true`)
194
+
195
+ The debug toolbar panels fetch data from these API endpoints. Create a controller and routes:
196
+
197
+ ```ts
198
+ // app/controllers/admin/debug_controller.ts
199
+ import app from '@adonisjs/core/services/app'
200
+ import type { HttpContext } from '@adonisjs/core/http'
201
+ import type { DebugStore } from 'adonisjs-server-stats/debug'
202
+
203
+ export default class DebugController {
204
+ private async getStore(): Promise<DebugStore | null> {
205
+ try {
206
+ return (await app.container.make('debug.store')) as DebugStore
207
+ } catch {
208
+ return null
209
+ }
210
+ }
211
+
212
+ async queries({ response }: HttpContext) {
213
+ const store = await this.getStore()
214
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
215
+ return response.json({ queries: store.queries.getLatest(500), summary: store.queries.getSummary() })
216
+ }
217
+
218
+ async events({ response }: HttpContext) {
219
+ const store = await this.getStore()
220
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
221
+ return response.json({ events: store.events.getLatest(200), total: store.events.getTotalCount() })
222
+ }
223
+
224
+ async routes({ response }: HttpContext) {
225
+ const store = await this.getStore()
226
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
227
+ return response.json({ routes: store.routes.getRoutes(), total: store.routes.getRouteCount() })
228
+ }
229
+
230
+ async logs({ response }: HttpContext) {
231
+ const store = await this.getStore()
232
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
233
+ return response.json({ logs: store.logs.getLatest(500), total: store.logs.getTotalCount() })
234
+ }
235
+
236
+ async emails({ response }: HttpContext) {
237
+ const store = await this.getStore()
238
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
239
+ const emails = store.emails.getLatest(100)
240
+ const stripped = emails.map(({ html, text, ...rest }) => rest)
241
+ return response.json({ emails: stripped, total: store.emails.getTotalCount() })
242
+ }
243
+
244
+ async emailPreview({ params, response }: HttpContext) {
245
+ const store = await this.getStore()
246
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
247
+ const html = store.emails.getEmailHtml(Number(params.id))
248
+ if (!html) return response.notFound({ error: 'Email not found' })
249
+ return response.header('Content-Type', 'text/html; charset=utf-8').send(html)
250
+ }
251
+
252
+ async traces({ response }: HttpContext) {
253
+ const store = await this.getStore()
254
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
255
+ if (!store.traces) return response.json({ traces: [], total: 0 })
256
+ const traces = store.traces.getLatest(100)
257
+ const list = traces.map(({ spans, warnings, ...rest }: any) => ({
258
+ ...rest,
259
+ warningCount: warnings.length,
260
+ }))
261
+ return response.json({ traces: list, total: store.traces.getTotalCount() })
262
+ }
263
+
264
+ async traceDetail({ params, response }: HttpContext) {
265
+ const store = await this.getStore()
266
+ if (!store) return response.notFound({ error: 'Debug toolbar not enabled' })
267
+ if (!store.traces) return response.notFound({ error: 'Tracing not enabled' })
268
+ const trace = store.traces.getTrace(Number(params.id))
269
+ if (!trace) return response.notFound({ error: 'Trace not found' })
270
+ return response.json(trace)
271
+ }
272
+ }
273
+ ```
274
+
275
+ ```ts
276
+ // start/routes.ts
277
+ router
278
+ .group(() => {
279
+ router.get('queries', '#controllers/admin/debug_controller.queries')
280
+ router.get('events', '#controllers/admin/debug_controller.events')
281
+ router.get('routes', '#controllers/admin/debug_controller.routes')
282
+ router.get('logs', '#controllers/admin/debug_controller.logs')
283
+ router.get('emails', '#controllers/admin/debug_controller.emails')
284
+ router.get('emails/:id/preview', '#controllers/admin/debug_controller.emailPreview')
285
+ router.get('traces', '#controllers/admin/debug_controller.traces')
286
+ router.get('traces/:id', '#controllers/admin/debug_controller.traceDetail')
287
+ })
288
+ .prefix('/admin/api/debug')
289
+ .use(middleware.superadmin())
290
+ ```
291
+
292
+ #### Dashboard routes (automatic -- when `devToolbar.dashboard: true`)
293
+
294
+ The full-page dashboard at `/__stats` **registers its own routes automatically** -- no manual route setup needed. The following routes are created under the configured `dashboardPath` (default: `/__stats`):
295
+
296
+ | Method | Path | Description |
297
+ |--------|------|-------------|
298
+ | GET | `/` | Dashboard page (HTML) |
299
+ | GET | `/api/overview` | Overview metrics |
300
+ | GET | `/api/overview/chart` | Time-series chart data |
301
+ | GET | `/api/requests` | Paginated request history |
302
+ | GET | `/api/requests/:id` | Request detail with queries/trace |
303
+ | GET | `/api/queries` | Paginated query list |
304
+ | GET | `/api/queries/grouped` | Queries grouped by normalized SQL |
305
+ | GET | `/api/queries/:id/explain` | EXPLAIN plan for a query |
306
+ | GET | `/api/events` | Paginated event list |
307
+ | GET | `/api/routes` | Route table |
308
+ | GET | `/api/logs` | Paginated log entries |
309
+ | GET | `/api/emails` | Paginated email list |
310
+ | GET | `/api/emails/:id/preview` | Email HTML preview |
311
+ | GET | `/api/traces` | Paginated trace list |
312
+ | GET | `/api/traces/:id` | Trace detail with spans |
313
+ | GET | `/api/cache` | Cache stats and key listing |
314
+ | GET | `/api/cache/:key` | Cache key detail |
315
+ | GET | `/api/jobs` | Job queue overview |
316
+ | GET | `/api/jobs/:id` | Job detail |
317
+ | POST | `/api/jobs/:id/retry` | Retry a failed job |
318
+ | GET | `/api/config` | App config (secrets redacted) |
319
+ | GET | `/api/filters` | Saved filters |
320
+ | POST | `/api/filters` | Create saved filter |
321
+ | DELETE | `/api/filters/:id` | Delete saved filter |
322
+
323
+ All dashboard routes are gated by the `shouldShow` callback if configured.
324
+
325
+ ### 5. Render the stats bar
194
326
 
195
327
  **Edge** (add before `</body>`):
196
328
 
@@ -204,36 +336,36 @@ export default class ServerStatsController {
204
336
 
205
337
  ### `ServerStatsConfig`
206
338
 
207
- | Option | Type | Default | Description |
208
- |---------------|------------------------|-----------------------------|--------------------------------------------|
209
- | `intervalMs` | `number` | `3000` | Collection + broadcast interval (ms) |
210
- | `transport` | `'transmit' \| 'none'` | `'transmit'` | SSE transport. `'none'` = poll-only. |
211
- | `channelName` | `string` | `'admin/server-stats'` | Transmit channel name |
212
- | `endpoint` | `string \| false` | `'/admin/api/server-stats'` | HTTP endpoint. `false` to disable. |
213
- | `collectors` | `MetricCollector[]` | `[]` | Array of collector instances |
214
- | `skipInTest` | `boolean` | `true` | Skip collection during tests |
215
- | `onStats` | `(stats) => void` | -- | Callback after each collection tick |
216
- | `shouldShow` | `(ctx) => boolean` | -- | Per-request visibility guard |
217
- | `devToolbar` | `DevToolbarOptions` | -- | Dev toolbar configuration |
339
+ | Option | Type | Default | Description |
340
+ | ------------- | ---------------------- | --------------------------- | ------------------------------------ |
341
+ | `intervalMs` | `number` | `3000` | Collection + broadcast interval (ms) |
342
+ | `transport` | `'transmit' \| 'none'` | `'transmit'` | SSE transport. `'none'` = poll-only. |
343
+ | `channelName` | `string` | `'admin/server-stats'` | Transmit channel name |
344
+ | `endpoint` | `string \| false` | `'/admin/api/server-stats'` | HTTP endpoint. `false` to disable. |
345
+ | `collectors` | `MetricCollector[]` | `[]` | Array of collector instances |
346
+ | `skipInTest` | `boolean` | `true` | Skip collection during tests |
347
+ | `onStats` | `(stats) => void` | -- | Callback after each collection tick |
348
+ | `shouldShow` | `(ctx) => boolean` | -- | Per-request visibility guard |
349
+ | `devToolbar` | `DevToolbarOptions` | -- | Dev toolbar configuration |
218
350
 
219
351
  ### `DevToolbarOptions`
220
352
 
221
- | Option | Type | Default | Description |
222
- |------------------------|-----------------|---------|------------------------------------------------|
223
- | `enabled` | `boolean` | `false` | Enable the dev toolbar |
224
- | `maxQueries` | `number` | `500` | Max SQL queries to buffer |
225
- | `maxEvents` | `number` | `200` | Max events to buffer |
226
- | `maxEmails` | `number` | `100` | Max emails to buffer |
227
- | `slowQueryThresholdMs` | `number` | `100` | Slow query threshold (ms) |
228
- | `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. |
229
- | `tracing` | `boolean` | `false` | Enable per-request tracing with timeline visualization |
230
- | `maxTraces` | `number` | `200` | Max request traces to buffer |
231
- | `dashboard` | `boolean` | `false` | Enable the full-page dashboard (requires `better-sqlite3`) |
232
- | `dashboardPath` | `string` | `'/__stats'` | URL path for the dashboard page |
233
- | `retentionDays` | `number` | `7` | Days to keep historical data in SQLite |
234
- | `dbPath` | `string` | `'.adonisjs/server-stats/dashboard.sqlite3'` | Path to the SQLite database file (relative to app root) |
235
- | `excludeFromTracing` | `string[]` | `[]` | URL prefixes to exclude from tracing and dashboard persistence. Requests still count toward HTTP metrics but won't appear in the timeline or be stored. The stats endpoint is always excluded automatically. |
236
- | `panes` | `DebugPane[]` | -- | Custom debug panel tabs |
353
+ | Option | Type | Default | Description |
354
+ | ---------------------- | ------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
355
+ | `enabled` | `boolean` | `false` | Enable the dev toolbar |
356
+ | `maxQueries` | `number` | `500` | Max SQL queries to buffer |
357
+ | `maxEvents` | `number` | `200` | Max events to buffer |
358
+ | `maxEmails` | `number` | `100` | Max emails to buffer |
359
+ | `slowQueryThresholdMs` | `number` | `100` | Slow query threshold (ms) |
360
+ | `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. |
361
+ | `tracing` | `boolean` | `false` | Enable per-request tracing with timeline visualization |
362
+ | `maxTraces` | `number` | `200` | Max request traces to buffer |
363
+ | `dashboard` | `boolean` | `false` | Enable the full-page dashboard (requires `better-sqlite3`) |
364
+ | `dashboardPath` | `string` | `'/__stats'` | URL path for the dashboard page |
365
+ | `retentionDays` | `number` | `7` | Days to keep historical data in SQLite |
366
+ | `dbPath` | `string` | `'.adonisjs/server-stats/dashboard.sqlite3'` | Path to the SQLite database file (relative to app root) |
367
+ | `excludeFromTracing` | `string[]` | `['/admin/api/debug', '/admin/api/server-stats']` | URL prefixes to exclude from tracing and dashboard persistence. Requests still count toward HTTP metrics but won't appear in the timeline or be stored. The stats endpoint is always excluded automatically. |
368
+ | `panes` | `DebugPane[]` | -- | Custom debug panel tabs |
237
369
 
238
370
  ---
239
371
 
@@ -243,27 +375,27 @@ Each collector is a factory function that returns a `MetricCollector`. All colle
243
375
 
244
376
  ### Built-in Collectors
245
377
 
246
- | Collector | Metrics | Options | Peer Deps |
247
- |--------------------------|-------------------------------------------------------------|------------|---------------------|
248
- | `processCollector()` | CPU %, event loop lag, heap/RSS memory, uptime, Node version | none | -- |
249
- | `systemCollector()` | OS load averages, system memory, system uptime | none | -- |
250
- | `httpCollector(opts?)` | Requests/sec, avg response time, error rate, active connections | optional | -- |
251
- | `dbPoolCollector(opts?)` | Pool used/free/pending/max connections | optional | `@adonisjs/lucid` |
252
- | `redisCollector()` | Status, memory, clients, keys, hit rate | none | `@adonisjs/redis` |
253
- | `queueCollector(opts)` | Active/waiting/delayed/failed jobs, worker count | **required** | `bullmq` |
254
- | `logCollector(opts)` | Errors/warnings/entries (5m window), entries/minute | **required** | -- |
255
- | `appCollector()` | Online users, pending webhooks, pending emails | none | `@adonisjs/lucid` |
378
+ | Collector | Metrics | Options | Peer Deps |
379
+ | ------------------------ | --------------------------------------------------------------- | ------------ | ----------------- |
380
+ | `processCollector()` | CPU %, event loop lag, heap/RSS memory, uptime, Node version | none | -- |
381
+ | `systemCollector()` | OS load averages, system memory, system uptime | none | -- |
382
+ | `httpCollector(opts?)` | Requests/sec, avg response time, error rate, active connections | optional | -- |
383
+ | `dbPoolCollector(opts?)` | Pool used/free/pending/max connections | optional | `@adonisjs/lucid` |
384
+ | `redisCollector()` | Status, memory, clients, keys, hit rate | none | `@adonisjs/redis` |
385
+ | `queueCollector(opts)` | Active/waiting/delayed/failed jobs, worker count | **required** | `bullmq` |
386
+ | `logCollector(opts)` | Errors/warnings/entries (5m window), entries/minute | **required** | -- |
387
+ | `appCollector()` | Online users, pending webhooks, pending emails | none | `@adonisjs/lucid` |
256
388
 
257
389
  ### Collector Options
258
390
 
259
391
  ```ts
260
392
  httpCollector({
261
- maxRecords: 10_000, // Circular buffer size (default: 10,000)
262
- windowMs: 60_000, // Rolling window for rate calc (default: 60s)
393
+ maxRecords: 10_000, // Circular buffer size (default: 10,000)
394
+ windowMs: 60_000, // Rolling window for rate calc (default: 60s)
263
395
  })
264
396
 
265
397
  dbPoolCollector({
266
- connectionName: 'postgres', // Lucid connection name (default: 'postgres')
398
+ connectionName: 'postgres', // Lucid connection name (default: 'postgres')
267
399
  })
268
400
 
269
401
  queueCollector({
@@ -305,7 +437,7 @@ function diskCollector(): MetricCollector {
305
437
  export default defineConfig({
306
438
  collectors: [
307
439
  processCollector(),
308
- diskCollector(), // mix with built-in collectors
440
+ diskCollector(), // mix with built-in collectors
309
441
  ],
310
442
  })
311
443
  ```
@@ -369,6 +501,7 @@ The `@serverStats()` Edge tag renders a self-contained stats bar with inline HTM
369
501
  ```
370
502
 
371
503
  Features:
504
+
372
505
  - Polls the stats API at the configured interval
373
506
  - Color-coded thresholds (green/amber/red)
374
507
  - SVG sparkline charts with gradient fills
@@ -392,37 +525,32 @@ export default defineConfig({
392
525
  maxEvents: 200,
393
526
  maxEmails: 100,
394
527
  slowQueryThresholdMs: 100,
395
- persistDebugData: true, // or a custom path: 'custom/debug.json'
396
- tracing: true, // enable per-request timeline
528
+ persistDebugData: true, // or a custom path: 'custom/debug.json'
529
+ tracing: true, // enable per-request timeline
397
530
  },
398
531
  })
399
532
  ```
400
533
 
401
- Register the debug API routes:
402
-
403
- ```ts
404
- // start/routes.ts
405
- router
406
- .group(() => {
407
- router.get('queries', '#controllers/admin/debug_controller.queries')
408
- router.get('events', '#controllers/admin/debug_controller.events')
409
- router.get('routes', '#controllers/admin/debug_controller.routes')
410
- router.get('emails', '#controllers/admin/debug_controller.emails')
411
- router.get('emails/:id/preview', '#controllers/admin/debug_controller.emailPreview')
412
- router.get('traces', '#controllers/admin/debug_controller.traces')
413
- router.get('traces/:id', '#controllers/admin/debug_controller.traceDetail')
414
- })
415
- .prefix('/admin/api/debug')
416
- .use(middleware.admin())
417
- ```
534
+ Register the debug API routes (see [step 4](#4-add-routes) for the full controller and route setup).
418
535
 
419
536
  ### Built-in Emails Tab
420
537
 
421
538
  The debug toolbar captures all emails sent via AdonisJS mail (`mail:sending`, `mail:sent`, `mail:queued`, `queued:mail:error` events). Click any email row to preview its HTML in an iframe.
422
539
 
540
+ > **Note:** Email previews are rendered in an iframe. If your app uses `@adonisjs/shield` with the default `X-Frame-Options: DENY` header, the preview will be blocked. Change it to `SAMEORIGIN` in your shield config:
541
+ >
542
+ > ```ts
543
+ > // config/shield.ts
544
+ > xFrame: {
545
+ > enabled: true,
546
+ > action: 'SAMEORIGIN',
547
+ > },
548
+ > ```
549
+
423
550
  ### Persistent Debug Data
424
551
 
425
552
  Enable `persistDebugData: true` to save queries, events, and emails to `.adonisjs/server-stats/debug-data.json`. You can also pass a custom path (relative to app root) like `persistDebugData: 'custom/debug.json'`. Data is:
553
+
426
554
  - **Loaded** on server startup (before collectors start)
427
555
  - **Flushed** every 30 seconds (handles crashes)
428
556
  - **Saved** on graceful shutdown
@@ -449,14 +577,14 @@ GET /organizations/create 286ms
449
577
 
450
578
  #### Span categories
451
579
 
452
- | Category | Color | Auto-captured |
453
- |----------|--------|---------------|
454
- | DB | Purple | `db:query` events |
580
+ | Category | Color | Auto-captured |
581
+ | -------- | ------ | ---------------------- |
582
+ | DB | Purple | `db:query` events |
455
583
  | Request | Blue | Full request lifecycle |
456
- | Mail | Green | -- |
457
- | Event | Amber | -- |
458
- | View | Cyan | -- |
459
- | Custom | Gray | Via `trace()` helper |
584
+ | Mail | Green | -- |
585
+ | Event | Amber | -- |
586
+ | View | Cyan | -- |
587
+ | Custom | Gray | Via `trace()` helper |
460
588
 
461
589
  #### Custom spans
462
590
 
@@ -527,19 +655,19 @@ devToolbar: {
527
655
 
528
656
  #### Dashboard Sections
529
657
 
530
- | Section | Description |
531
- |------------|-------------|
532
- | **Overview** | Performance cards (avg/p95 response time, req/min, error rate) with sparkline charts and configurable time ranges (1h/6h/24h/7d) |
533
- | **Requests** | Paginated request history with method, URL, status, duration. Click for detail view with associated queries and trace |
658
+ | Section | Description |
659
+ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
660
+ | **Overview** | Performance cards (avg/p95 response time, req/min, error rate) with sparkline charts and configurable time ranges (1h/6h/24h/7d) |
661
+ | **Requests** | Paginated request history with method, URL, status, duration. Click for detail view with associated queries and trace |
534
662
  | **Queries** | All captured SQL queries with duration, model, connection. Grouped view shows query patterns by normalized SQL. EXPLAIN plan support for SELECT queries |
535
- | **Events** | Application events captured from the AdonisJS emitter |
536
- | **Routes** | Full route table with methods, patterns, handlers, and middleware stacks |
537
- | **Logs** | Log history with level filtering, text search, and structured JSON field search (e.g. filter by `userId = 5`) |
538
- | **Emails** | Email history with sender, recipient, subject, status. Click for HTML preview in iframe |
539
- | **Timeline** | Per-request waterfall timeline (requires `tracing: true`) |
540
- | **Cache** | Redis key browser with SCAN-based listing, type-aware detail view, and server stats (requires `@adonisjs/redis`) |
541
- | **Jobs** | Queue overview with job listing, detail, and retry for failed jobs (requires `@rlanz/bull-queue`) |
542
- | **Config** | Sanitized view of app configuration and environment variables. Secrets are auto-redacted |
663
+ | **Events** | Application events captured from the AdonisJS emitter |
664
+ | **Routes** | Full route table with methods, patterns, handlers, and middleware stacks |
665
+ | **Logs** | Log history with level filtering, text search, and structured JSON field search (e.g. filter by `userId = 5`) |
666
+ | **Emails** | Email history with sender, recipient, subject, status. Click for HTML preview in iframe |
667
+ | **Timeline** | Per-request waterfall timeline (requires `tracing: true`) |
668
+ | **Cache** | Redis key browser with SCAN-based listing, type-aware detail view, and server stats (requires `@adonisjs/redis`) |
669
+ | **Jobs** | Queue overview with job listing, detail, and retry for failed jobs (requires `@rlanz/bull-queue`) |
670
+ | **Config** | Sanitized view of app configuration and environment variables. Secrets are auto-redacted |
543
671
 
544
672
  #### Access Control
545
673
 
@@ -562,12 +690,14 @@ When the dashboard is enabled, the debug panel gains link icons on query, event,
562
690
  #### Real-Time Updates
563
691
 
564
692
  The dashboard supports real-time updates via two mechanisms:
693
+
565
694
  - **Transmit (SSE)**: If `@adonisjs/transmit` is installed, the dashboard subscribes to `server-stats/dashboard` for live overview updates
566
695
  - **Polling fallback**: If Transmit is not available, the dashboard polls the API at a configurable interval
567
696
 
568
697
  #### Data Storage
569
698
 
570
699
  The dashboard uses a dedicated SQLite database (separate from your app's database) with 8 tables prefixed with `server_stats_`. The database is:
700
+
571
701
  - **Auto-migrated** on startup (no manual migration step)
572
702
  - **Self-contained** -- uses its own Knex connection, never touches your app's migration history
573
703
  - **Self-cleaning** -- old data is automatically purged based on `retentionDays`
@@ -640,40 +770,40 @@ async webhooks({ response }: HttpContext) {
640
770
 
641
771
  #### `DebugPane` Options
642
772
 
643
- | Option | Type | Default | Description |
644
- |-------------|---------------------|---------|----------------------------------------------|
645
- | `id` | `string` | -- | Unique identifier (also default data key) |
646
- | `label` | `string` | -- | Tab display name |
647
- | `endpoint` | `string` | -- | API endpoint URL |
648
- | `columns` | `DebugPaneColumn[]` | -- | Column definitions |
649
- | `search` | `{ placeholder }` | -- | Enable search bar |
650
- | `dataKey` | `string` | `id` | JSON key for data array (dot notation OK) |
651
- | `fetchOnce` | `boolean` | `false` | Cache after first fetch |
652
- | `clearable` | `boolean` | `false` | Show Clear button |
773
+ | Option | Type | Default | Description |
774
+ | ----------- | ------------------- | ------- | ----------------------------------------- |
775
+ | `id` | `string` | -- | Unique identifier (also default data key) |
776
+ | `label` | `string` | -- | Tab display name |
777
+ | `endpoint` | `string` | -- | API endpoint URL |
778
+ | `columns` | `DebugPaneColumn[]` | -- | Column definitions |
779
+ | `search` | `{ placeholder }` | -- | Enable search bar |
780
+ | `dataKey` | `string` | `id` | JSON key for data array (dot notation OK) |
781
+ | `fetchOnce` | `boolean` | `false` | Cache after first fetch |
782
+ | `clearable` | `boolean` | `false` | Show Clear button |
653
783
 
654
784
  #### `DebugPaneColumn` Options
655
785
 
656
- | Option | Type | Default | Description |
657
- |-----------------|--------------------------|----------|------------------------------------------|
658
- | `key` | `string` | -- | JSON field name |
659
- | `label` | `string` | -- | Column header text |
660
- | `width` | `string` | auto | CSS width (e.g. `'60px'`) |
661
- | `format` | `DebugPaneFormatType` | `'text'` | Cell format (see table below) |
662
- | `searchable` | `boolean` | `false` | Include in search filtering |
663
- | `filterable` | `boolean` | `false` | Click to set as search filter |
664
- | `badgeColorMap` | `Record<string, string>` | -- | Value-to-color map for `badge` format |
786
+ | Option | Type | Default | Description |
787
+ | --------------- | ------------------------ | -------- | ------------------------------------- |
788
+ | `key` | `string` | -- | JSON field name |
789
+ | `label` | `string` | -- | Column header text |
790
+ | `width` | `string` | auto | CSS width (e.g. `'60px'`) |
791
+ | `format` | `DebugPaneFormatType` | `'text'` | Cell format (see table below) |
792
+ | `searchable` | `boolean` | `false` | Include in search filtering |
793
+ | `filterable` | `boolean` | `false` | Click to set as search filter |
794
+ | `badgeColorMap` | `Record<string, string>` | -- | Value-to-color map for `badge` format |
665
795
 
666
796
  #### Format Types
667
797
 
668
- | Format | Renders As | Expected Input |
669
- |------------|----------------------------------------|-------------------------|
670
- | `text` | Escaped plain text | any |
671
- | `time` | `HH:MM:SS.mmm` | Unix timestamp (ms) |
672
- | `timeAgo` | `3s ago`, `2m ago` | Unix timestamp (ms) |
673
- | `duration` | `X.XXms` with color coding | number (ms) |
674
- | `method` | HTTP method pill badge | `'GET'`, `'POST'`, etc. |
675
- | `json` | Compact preview, click to expand | object or array |
676
- | `badge` | Colored pill via `badgeColorMap` | string |
798
+ | Format | Renders As | Expected Input |
799
+ | ---------- | -------------------------------- | ----------------------- |
800
+ | `text` | Escaped plain text | any |
801
+ | `time` | `HH:MM:SS.mmm` | Unix timestamp (ms) |
802
+ | `timeAgo` | `3s ago`, `2m ago` | Unix timestamp (ms) |
803
+ | `duration` | `X.XXms` with color coding | number (ms) |
804
+ | `method` | HTTP method pill badge | `'GET'`, `'POST'`, etc. |
805
+ | `json` | Compact preview, click to expand | object or array |
806
+ | `badge` | Colored pill via `badgeColorMap` | string |
677
807
 
678
808
  Badge colors: `green`, `amber`, `red`, `blue`, `purple`, `muted`
679
809
 
@@ -704,6 +834,7 @@ Gauges are updated automatically on each collection tick.
704
834
  The log stream module watches a JSON log file and broadcasts new entries via Transmit (SSE).
705
835
 
706
836
  **Two purposes:**
837
+
707
838
  1. Provides error/warning counts to the stats bar via `logCollector()`
708
839
  2. Broadcasts individual log entries to a Transmit channel via `LogStreamProvider`
709
840
 
@@ -778,7 +909,6 @@ import type {
778
909
  QueueRedisConnection,
779
910
  LogCollectorOptions,
780
911
  } from 'adonisjs-server-stats/collectors'
781
-
782
912
  ```
783
913
 
784
914
  ---
@@ -787,16 +917,16 @@ import type {
787
917
 
788
918
  All integrations use lazy `import()` -- missing peer deps won't crash the app. The corresponding collector simply returns defaults.
789
919
 
790
- | Dependency | Required By |
791
- |-----------------------------|------------------------------------------------|
792
- | `@adonisjs/core` | Everything (required) |
793
- | `@adonisjs/lucid` | `dbPoolCollector`, `appCollector`, dashboard |
794
- | `@adonisjs/redis` | `redisCollector`, dashboard cache inspector |
795
- | `@adonisjs/transmit` | Provider (SSE broadcast), dashboard real-time |
796
- | `@julr/adonisjs-prometheus` | `serverStatsCollector` |
797
- | `bullmq` | `queueCollector` |
798
- | `better-sqlite3` | Dashboard (`dashboard: true`) |
799
- | `edge.js` | Edge tag |
920
+ | Dependency | Required By |
921
+ | --------------------------- | --------------------------------------------- |
922
+ | `@adonisjs/core` | Everything (required) |
923
+ | `@adonisjs/lucid` | `dbPoolCollector`, `appCollector`, dashboard |
924
+ | `@adonisjs/redis` | `redisCollector`, dashboard cache inspector |
925
+ | `@adonisjs/transmit` | Provider (SSE broadcast), dashboard real-time |
926
+ | `@julr/adonisjs-prometheus` | `serverStatsCollector` |
927
+ | `bullmq` | `queueCollector` |
928
+ | `better-sqlite3` | Dashboard (`dashboard: true`) |
929
+ | `edge.js` | Edge tag |
800
930
 
801
931
  ## License
802
932
 
@@ -1 +1 @@
1
- {"version":3,"file":"configure.d.ts","sourceRoot":"","sources":["../configure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,mCAAmC,CAAA;AAI9D,wBAAsB,SAAS,CAAC,OAAO,EAAE,SAAS,iBAWjD"}
1
+ {"version":3,"file":"configure.d.ts","sourceRoot":"","sources":["../configure.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,SAAS,MAAM,mCAAmC,CAAA;AAE9D,wBAAsB,SAAS,CAAC,OAAO,EAAE,SAAS,iBAWjD"}
@@ -1,5 +1,5 @@
1
- import type { HttpContext } from "@adonisjs/core/http";
2
- import type { DebugStore } from "../debug/debug_store.js";
1
+ import type { DebugStore } from '../debug/debug_store.js';
2
+ import type { HttpContext } from '@adonisjs/core/http';
3
3
  export default class DebugController {
4
4
  private store;
5
5
  constructor(store: DebugStore);
@@ -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;IAS9C,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;IAahC,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,WAAW;CAWpD"}
1
+ {"version":3,"file":"debug_controller.d.ts","sourceRoot":"","sources":["../../../src/controller/debug_controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,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"}
@@ -1,5 +1,5 @@
1
- import type { HttpContext } from '@adonisjs/core/http';
2
1
  import type { StatsEngine } from '../engine/stats_engine.js';
2
+ import type { HttpContext } from '@adonisjs/core/http';
3
3
  export default class ServerStatsController {
4
4
  private engine;
5
5
  constructor(engine: StatsEngine);
@@ -1 +1 @@
1
- {"version":3,"file":"server_stats_controller.d.ts","sourceRoot":"","sources":["../../../src/controller/server_stats_controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAE5D,MAAM,CAAC,OAAO,OAAO,qBAAqB;IAC5B,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,WAAW;IAEjC,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;CAItC"}
1
+ {"version":3,"file":"server_stats_controller.d.ts","sourceRoot":"","sources":["../../../src/controller/server_stats_controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,MAAM,CAAC,OAAO,OAAO,qBAAqB;IAC5B,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,WAAW;IAEjC,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE,WAAW;CAItC"}
@@ -1 +1 @@
1
- {"version":3,"file":"chart_aggregator.d.ts","sourceRoot":"","sources":["../../../src/dashboard/chart_aggregator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAEhC;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAAM;IAChB,OAAO,CAAC,KAAK,CAA8C;gBAE/C,EAAE,EAAE,IAAI;IAIpB,KAAK,IAAI,IAAI;IAQb,IAAI,IAAI,IAAI;YAOE,SAAS;CA4DxB"}
1
+ {"version":3,"file":"chart_aggregator.d.ts","sourceRoot":"","sources":["../../../src/dashboard/chart_aggregator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAEhC;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAAM;IAChB,OAAO,CAAC,KAAK,CAA8C;gBAE/C,EAAE,EAAE,IAAI;IAIpB,KAAK,IAAI,IAAI;IAQb,IAAI,IAAI,IAAI;YAOE,SAAS;CA0DxB"}