adonisjs-server-stats 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +537 -0
- package/dist/configure.d.ts +3 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +11 -0
- package/dist/src/collectors/app_collector.d.ts +17 -0
- package/dist/src/collectors/app_collector.d.ts.map +1 -0
- package/dist/src/collectors/app_collector.js +53 -0
- package/dist/src/collectors/collector.d.ts +67 -0
- package/dist/src/collectors/collector.d.ts.map +1 -0
- package/dist/src/collectors/collector.js +1 -0
- package/dist/src/collectors/db_pool_collector.d.ts +37 -0
- package/dist/src/collectors/db_pool_collector.d.ts.map +1 -0
- package/dist/src/collectors/db_pool_collector.js +49 -0
- package/dist/src/collectors/http_collector.d.ts +60 -0
- package/dist/src/collectors/http_collector.d.ts.map +1 -0
- package/dist/src/collectors/http_collector.js +56 -0
- package/dist/src/collectors/index.d.ts +14 -0
- package/dist/src/collectors/index.d.ts.map +1 -0
- package/dist/src/collectors/index.js +8 -0
- package/dist/src/collectors/log_collector.d.ts +48 -0
- package/dist/src/collectors/log_collector.d.ts.map +1 -0
- package/dist/src/collectors/log_collector.js +55 -0
- package/dist/src/collectors/process_collector.d.ts +20 -0
- package/dist/src/collectors/process_collector.d.ts.map +1 -0
- package/dist/src/collectors/process_collector.js +61 -0
- package/dist/src/collectors/queue_collector.d.ts +67 -0
- package/dist/src/collectors/queue_collector.d.ts.map +1 -0
- package/dist/src/collectors/queue_collector.js +57 -0
- package/dist/src/collectors/redis_collector.d.ts +18 -0
- package/dist/src/collectors/redis_collector.d.ts.map +1 -0
- package/dist/src/collectors/redis_collector.js +68 -0
- package/dist/src/collectors/system_collector.d.ts +14 -0
- package/dist/src/collectors/system_collector.d.ts.map +1 -0
- package/dist/src/collectors/system_collector.js +28 -0
- package/dist/src/controller/debug_controller.d.ts +10 -0
- package/dist/src/controller/debug_controller.d.ts.map +1 -0
- package/dist/src/controller/debug_controller.js +19 -0
- package/dist/src/controller/server_stats_controller.d.ts +8 -0
- package/dist/src/controller/server_stats_controller.d.ts.map +1 -0
- package/dist/src/controller/server_stats_controller.js +10 -0
- package/dist/src/debug/debug_store.d.ts +17 -0
- package/dist/src/debug/debug_store.d.ts.map +1 -0
- package/dist/src/debug/debug_store.js +26 -0
- package/dist/src/debug/event_collector.d.ts +20 -0
- package/dist/src/debug/event_collector.d.ts.map +1 -0
- package/dist/src/debug/event_collector.js +84 -0
- package/dist/src/debug/query_collector.d.ts +27 -0
- package/dist/src/debug/query_collector.d.ts.map +1 -0
- package/dist/src/debug/query_collector.js +80 -0
- package/dist/src/debug/ring_buffer.d.ts +21 -0
- package/dist/src/debug/ring_buffer.d.ts.map +1 -0
- package/dist/src/debug/ring_buffer.js +50 -0
- package/dist/src/debug/route_inspector.d.ts +18 -0
- package/dist/src/debug/route_inspector.d.ts.map +1 -0
- package/dist/src/debug/route_inspector.js +97 -0
- package/dist/src/debug/types.d.ts +257 -0
- package/dist/src/debug/types.d.ts.map +1 -0
- package/dist/src/debug/types.js +4 -0
- package/dist/src/define_config.d.ts +24 -0
- package/dist/src/define_config.d.ts.map +1 -0
- package/dist/src/define_config.js +24 -0
- package/dist/src/edge/client/debug-panel.css +332 -0
- package/dist/src/edge/client/debug-panel.js +738 -0
- package/dist/src/edge/client/stats-bar.css +229 -0
- package/dist/src/edge/client/stats-bar.js +488 -0
- package/dist/src/edge/plugin.d.ts +22 -0
- package/dist/src/edge/plugin.d.ts.map +1 -0
- package/dist/src/edge/plugin.js +127 -0
- package/dist/src/edge/views/debug-panel.edge +69 -0
- package/dist/src/edge/views/stats-bar.edge +47 -0
- package/dist/src/engine/request_metrics.d.ts +22 -0
- package/dist/src/engine/request_metrics.d.ts.map +1 -0
- package/dist/src/engine/request_metrics.js +58 -0
- package/dist/src/engine/stats_engine.d.ts +51 -0
- package/dist/src/engine/stats_engine.d.ts.map +1 -0
- package/dist/src/engine/stats_engine.js +71 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +3 -0
- package/dist/src/log_stream/log_stream_provider.d.ts +9 -0
- package/dist/src/log_stream/log_stream_provider.d.ts.map +1 -0
- package/dist/src/log_stream/log_stream_provider.js +33 -0
- package/dist/src/log_stream/log_stream_service.d.ts +15 -0
- package/dist/src/log_stream/log_stream_service.d.ts.map +1 -0
- package/dist/src/log_stream/log_stream_service.js +103 -0
- package/dist/src/middleware/request_tracking_middleware.d.ts +7 -0
- package/dist/src/middleware/request_tracking_middleware.d.ts.map +1 -0
- package/dist/src/middleware/request_tracking_middleware.js +41 -0
- package/dist/src/prometheus/prometheus_collector.d.ts +9 -0
- package/dist/src/prometheus/prometheus_collector.d.ts.map +1 -0
- package/dist/src/prometheus/prometheus_collector.js +204 -0
- package/dist/src/provider/server_stats_provider.d.ts +13 -0
- package/dist/src/provider/server_stats_provider.d.ts.map +1 -0
- package/dist/src/provider/server_stats_provider.js +114 -0
- package/dist/src/stubs/config.stub +39 -0
- package/dist/src/stubs/main.d.ts +2 -0
- package/dist/src/stubs/main.d.ts.map +1 -0
- package/dist/src/stubs/main.js +5 -0
- package/dist/src/types.d.ts +354 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1 -0
- package/package.json +123 -0
- package/screenshots/debug-emails.png +0 -0
- package/screenshots/debug-events.png +0 -0
- package/screenshots/debug-logs.png +0 -0
- package/screenshots/debug-queries.png +0 -0
- package/screenshots/debug-routes.png +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
# adonisjs-server-stats
|
|
2
|
+
|
|
3
|
+
A Laravel Telescope-inspired dev toolbar and real-time server monitor for **AdonisJS v6**.
|
|
4
|
+
|
|
5
|
+
Drop a single Edge tag into your layout and get a live stats bar showing CPU, memory, requests/sec, database pool, Redis, queues, and logs -- plus a full debug toolbar with SQL query inspection, event tracing, route listing, live log tailing, and custom panels.
|
|
6
|
+
|
|
7
|
+
Zero frontend dependencies. Zero build step. Just `@serverStats()` and go.
|
|
8
|
+
|
|
9
|
+
## Screenshots
|
|
10
|
+
|
|
11
|
+
**Stats bar** -- always-on metrics strip at the bottom of every page:
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
**Debug toolbar** -- expandable panels for deep inspection:
|
|
16
|
+
|
|
17
|
+
| Queries | Events |
|
|
18
|
+
|---------|--------|
|
|
19
|
+
|  |  |
|
|
20
|
+
|
|
21
|
+
| Routes | Logs |
|
|
22
|
+
|--------|------|
|
|
23
|
+
|  |  |
|
|
24
|
+
|
|
25
|
+
| Emails (custom pane) |
|
|
26
|
+
|----------------------|
|
|
27
|
+
|  |
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- **Live stats bar** -- CPU, memory, event loop lag, HTTP throughput, DB pool, Redis, queues, logs
|
|
32
|
+
- **Debug toolbar** -- SQL queries, events, routes, logs with search and filtering
|
|
33
|
+
- **Custom panes** -- add your own tabs (webhooks, emails, cache, anything) with a simple config
|
|
34
|
+
- **Pluggable collectors** -- use built-in collectors or write your own
|
|
35
|
+
- **Visibility control** -- show only to admins, specific roles, or in dev mode
|
|
36
|
+
- **SSE broadcasting** -- real-time updates via AdonisJS Transmit
|
|
37
|
+
- **Prometheus export** -- expose all metrics as Prometheus gauges
|
|
38
|
+
- **Self-contained** -- inline HTML/CSS/JS Edge tag, no React, no external assets
|
|
39
|
+
- **Graceful degradation** -- missing optional dependencies are handled automatically
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
node ace configure adonisjs-server-stats
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This publishes `config/server_stats.ts` and registers the providers in `adonisrc.ts`.
|
|
48
|
+
|
|
49
|
+
Or install manually:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install adonisjs-server-stats
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
### 1. Register providers
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// adonisrc.ts
|
|
61
|
+
providers: [
|
|
62
|
+
{
|
|
63
|
+
file: () => import('adonisjs-server-stats/provider'),
|
|
64
|
+
environment: ['web'],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
file: () => import('adonisjs-server-stats/log-stream/provider'),
|
|
68
|
+
environment: ['web'],
|
|
69
|
+
},
|
|
70
|
+
]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Register middleware
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// start/kernel.ts
|
|
77
|
+
server.use([
|
|
78
|
+
() => import('adonisjs-server-stats/middleware'),
|
|
79
|
+
])
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. Create config
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// config/server_stats.ts
|
|
86
|
+
import { defineConfig } from 'adonisjs-server-stats'
|
|
87
|
+
import {
|
|
88
|
+
processCollector,
|
|
89
|
+
systemCollector,
|
|
90
|
+
httpCollector,
|
|
91
|
+
dbPoolCollector,
|
|
92
|
+
redisCollector,
|
|
93
|
+
queueCollector,
|
|
94
|
+
logCollector,
|
|
95
|
+
appCollector,
|
|
96
|
+
} from 'adonisjs-server-stats/collectors'
|
|
97
|
+
import env from '#start/env'
|
|
98
|
+
|
|
99
|
+
export default defineConfig({
|
|
100
|
+
intervalMs: 3000,
|
|
101
|
+
transport: 'transmit',
|
|
102
|
+
channelName: 'admin/server-stats',
|
|
103
|
+
endpoint: '/admin/api/server-stats',
|
|
104
|
+
shouldShow: (ctx) => !!ctx.auth?.user?.isAdmin,
|
|
105
|
+
collectors: [
|
|
106
|
+
processCollector(),
|
|
107
|
+
systemCollector(),
|
|
108
|
+
httpCollector({ maxRecords: 10_000 }),
|
|
109
|
+
dbPoolCollector({ connectionName: 'postgres' }),
|
|
110
|
+
redisCollector(),
|
|
111
|
+
queueCollector({
|
|
112
|
+
queueName: 'default',
|
|
113
|
+
connection: {
|
|
114
|
+
host: env.get('QUEUE_REDIS_HOST'),
|
|
115
|
+
port: env.get('QUEUE_REDIS_PORT'),
|
|
116
|
+
password: env.get('QUEUE_REDIS_PASSWORD'),
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
logCollector({ logPath: 'logs/adonisjs.log' }),
|
|
120
|
+
appCollector(),
|
|
121
|
+
],
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### 4. Add a route
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
// start/routes.ts
|
|
129
|
+
router
|
|
130
|
+
.get('/admin/api/server-stats', '#controllers/admin/server_stats_controller.index')
|
|
131
|
+
.use(middleware.superadmin())
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 5. Create the controller
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
// app/controllers/admin/server_stats_controller.ts
|
|
138
|
+
import app from '@adonisjs/core/services/app'
|
|
139
|
+
import type { HttpContext } from '@adonisjs/core/http'
|
|
140
|
+
import type { StatsEngine } from 'adonisjs-server-stats'
|
|
141
|
+
|
|
142
|
+
export default class ServerStatsController {
|
|
143
|
+
async index({ response }: HttpContext) {
|
|
144
|
+
const engine = await app.container.make('server_stats.engine') as StatsEngine
|
|
145
|
+
return response.json(engine.getLatestStats())
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 6. Render the stats bar
|
|
151
|
+
|
|
152
|
+
**Edge** (add before `</body>`):
|
|
153
|
+
|
|
154
|
+
```edge
|
|
155
|
+
@serverStats()
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Config Reference
|
|
161
|
+
|
|
162
|
+
### `ServerStatsConfig`
|
|
163
|
+
|
|
164
|
+
| Option | Type | Default | Description |
|
|
165
|
+
|---------------|------------------------|-----------------------------|--------------------------------------------|
|
|
166
|
+
| `intervalMs` | `number` | `3000` | Collection + broadcast interval (ms) |
|
|
167
|
+
| `transport` | `'transmit' \| 'none'` | `'transmit'` | SSE transport. `'none'` = poll-only. |
|
|
168
|
+
| `channelName` | `string` | `'admin/server-stats'` | Transmit channel name |
|
|
169
|
+
| `endpoint` | `string \| false` | `'/admin/api/server-stats'` | HTTP endpoint. `false` to disable. |
|
|
170
|
+
| `collectors` | `MetricCollector[]` | `[]` | Array of collector instances |
|
|
171
|
+
| `skipInTest` | `boolean` | `true` | Skip collection during tests |
|
|
172
|
+
| `onStats` | `(stats) => void` | -- | Callback after each collection tick |
|
|
173
|
+
| `shouldShow` | `(ctx) => boolean` | -- | Per-request visibility guard |
|
|
174
|
+
| `devToolbar` | `DevToolbarOptions` | -- | Dev toolbar configuration |
|
|
175
|
+
|
|
176
|
+
### `DevToolbarOptions`
|
|
177
|
+
|
|
178
|
+
| Option | Type | Default | Description |
|
|
179
|
+
|------------------------|-----------------|---------|------------------------------------|
|
|
180
|
+
| `enabled` | `boolean` | `false` | Enable the dev toolbar |
|
|
181
|
+
| `maxQueries` | `number` | `500` | Max SQL queries to buffer |
|
|
182
|
+
| `maxEvents` | `number` | `200` | Max events to buffer |
|
|
183
|
+
| `slowQueryThresholdMs` | `number` | `100` | Slow query threshold (ms) |
|
|
184
|
+
| `panes` | `DebugPane[]` | -- | Custom debug panel tabs |
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Collectors
|
|
189
|
+
|
|
190
|
+
Each collector is a factory function that returns a `MetricCollector`. All collectors run in parallel each tick; missing peer dependencies are handled gracefully (the collector returns defaults instead of crashing).
|
|
191
|
+
|
|
192
|
+
### Built-in Collectors
|
|
193
|
+
|
|
194
|
+
| Collector | Metrics | Options | Peer Deps |
|
|
195
|
+
|--------------------------|-------------------------------------------------------------|------------|---------------------|
|
|
196
|
+
| `processCollector()` | CPU %, event loop lag, heap/RSS memory, uptime, Node version | none | -- |
|
|
197
|
+
| `systemCollector()` | OS load averages, system memory, system uptime | none | -- |
|
|
198
|
+
| `httpCollector(opts?)` | Requests/sec, avg response time, error rate, active connections | optional | -- |
|
|
199
|
+
| `dbPoolCollector(opts?)` | Pool used/free/pending/max connections | optional | `@adonisjs/lucid` |
|
|
200
|
+
| `redisCollector()` | Status, memory, clients, keys, hit rate | none | `@adonisjs/redis` |
|
|
201
|
+
| `queueCollector(opts)` | Active/waiting/delayed/failed jobs, worker count | **required** | `bullmq` |
|
|
202
|
+
| `logCollector(opts)` | Errors/warnings/entries (5m window), entries/minute | **required** | -- |
|
|
203
|
+
| `appCollector()` | Online users, pending webhooks, pending emails | none | `@adonisjs/lucid` |
|
|
204
|
+
|
|
205
|
+
### Collector Options
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
httpCollector({
|
|
209
|
+
maxRecords: 10_000, // Circular buffer size (default: 10,000)
|
|
210
|
+
windowMs: 60_000, // Rolling window for rate calc (default: 60s)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
dbPoolCollector({
|
|
214
|
+
connectionName: 'postgres', // Lucid connection name (default: 'postgres')
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
queueCollector({
|
|
218
|
+
queueName: 'default',
|
|
219
|
+
connection: {
|
|
220
|
+
host: 'localhost',
|
|
221
|
+
port: 6379,
|
|
222
|
+
password: 'secret',
|
|
223
|
+
},
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
logCollector({
|
|
227
|
+
logPath: 'logs/adonisjs.log',
|
|
228
|
+
})
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Custom Collectors
|
|
232
|
+
|
|
233
|
+
Implement the `MetricCollector` interface to create your own:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
import type { MetricCollector } from 'adonisjs-server-stats'
|
|
237
|
+
|
|
238
|
+
function diskCollector(): MetricCollector {
|
|
239
|
+
return {
|
|
240
|
+
name: 'disk',
|
|
241
|
+
async collect() {
|
|
242
|
+
const { availableSpace, totalSpace } = await getDiskInfo()
|
|
243
|
+
return {
|
|
244
|
+
diskAvailableGb: availableSpace / 1e9,
|
|
245
|
+
diskTotalGb: totalSpace / 1e9,
|
|
246
|
+
diskUsagePercent: ((totalSpace - availableSpace) / totalSpace) * 100,
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// config/server_stats.ts
|
|
253
|
+
export default defineConfig({
|
|
254
|
+
collectors: [
|
|
255
|
+
processCollector(),
|
|
256
|
+
diskCollector(), // mix with built-in collectors
|
|
257
|
+
],
|
|
258
|
+
})
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The `MetricCollector` interface:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
interface MetricCollector {
|
|
265
|
+
name: string
|
|
266
|
+
start?(): void | Promise<void>
|
|
267
|
+
stop?(): void | Promise<void>
|
|
268
|
+
collect(): Record<string, MetricValue> | Promise<Record<string, MetricValue>>
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Visibility Control (`shouldShow`)
|
|
275
|
+
|
|
276
|
+
Control who sees the stats bar via the `shouldShow` config callback. It receives the AdonisJS `HttpContext` and returns `true` to render.
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
export default defineConfig({
|
|
280
|
+
// Only admin users
|
|
281
|
+
shouldShow: (ctx) => !!ctx.auth?.user?.isAdmin,
|
|
282
|
+
|
|
283
|
+
// Only in development
|
|
284
|
+
shouldShow: () => process.env.NODE_ENV === 'development',
|
|
285
|
+
|
|
286
|
+
// Multiple roles
|
|
287
|
+
shouldShow: (ctx) =>
|
|
288
|
+
ctx.auth?.user?.role === 'admin' || ctx.auth?.user?.role === 'superadmin',
|
|
289
|
+
})
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
When `shouldShow` is not set, the stats bar always renders.
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Edge Tag
|
|
297
|
+
|
|
298
|
+
The `@serverStats()` Edge tag renders a self-contained stats bar with inline HTML, CSS, and JS -- no external assets, no build step.
|
|
299
|
+
|
|
300
|
+
```edge
|
|
301
|
+
<body>
|
|
302
|
+
@inertia()
|
|
303
|
+
@serverStats()
|
|
304
|
+
</body>
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Features:
|
|
308
|
+
- Polls the stats API at the configured interval
|
|
309
|
+
- Color-coded thresholds (green/amber/red)
|
|
310
|
+
- SVG sparkline charts with gradient fills
|
|
311
|
+
- Hover tooltips with min/max/avg stats
|
|
312
|
+
- Show/hide toggle (persisted via localStorage)
|
|
313
|
+
- Auto-hides for non-admin users (403 detection)
|
|
314
|
+
- Scoped CSS (`.ss-` prefix)
|
|
315
|
+
- Stale connection indicator (amber dot after 10s)
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Dev Toolbar
|
|
320
|
+
|
|
321
|
+
Adds a debug panel with SQL query inspection, event tracking, route table, and live logs. Only active in non-production environments.
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
export default defineConfig({
|
|
325
|
+
devToolbar: {
|
|
326
|
+
enabled: true,
|
|
327
|
+
maxQueries: 500,
|
|
328
|
+
maxEvents: 200,
|
|
329
|
+
slowQueryThresholdMs: 100,
|
|
330
|
+
},
|
|
331
|
+
})
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Register the debug API routes:
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
// start/routes.ts
|
|
338
|
+
router
|
|
339
|
+
.group(() => {
|
|
340
|
+
router.get('queries', '#controllers/admin/debug_controller.queries')
|
|
341
|
+
router.get('events', '#controllers/admin/debug_controller.events')
|
|
342
|
+
router.get('routes', '#controllers/admin/debug_controller.routes')
|
|
343
|
+
})
|
|
344
|
+
.prefix('/admin/api/debug')
|
|
345
|
+
.use(middleware.admin())
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Custom Debug Panes
|
|
349
|
+
|
|
350
|
+
Add custom tabs to the debug panel:
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
import { defineConfig } from 'adonisjs-server-stats'
|
|
354
|
+
import type { DebugPane } from 'adonisjs-server-stats'
|
|
355
|
+
|
|
356
|
+
const webhooksPane: DebugPane = {
|
|
357
|
+
id: 'webhooks',
|
|
358
|
+
label: 'Webhooks',
|
|
359
|
+
endpoint: '/admin/api/debug/webhooks',
|
|
360
|
+
columns: [
|
|
361
|
+
{ key: 'id', label: '#', width: '40px' },
|
|
362
|
+
{ key: 'event', label: 'Event', searchable: true },
|
|
363
|
+
{ key: 'url', label: 'URL', searchable: true },
|
|
364
|
+
{
|
|
365
|
+
key: 'status',
|
|
366
|
+
label: 'Status',
|
|
367
|
+
width: '80px',
|
|
368
|
+
format: 'badge',
|
|
369
|
+
badgeColorMap: { delivered: 'green', pending: 'amber', failed: 'red' },
|
|
370
|
+
},
|
|
371
|
+
{ key: 'duration', label: 'Duration', width: '70px', format: 'duration' },
|
|
372
|
+
{ key: 'timestamp', label: 'Time', width: '80px', format: 'timeAgo' },
|
|
373
|
+
],
|
|
374
|
+
search: { placeholder: 'Filter webhooks by event or URL...' },
|
|
375
|
+
clearable: true,
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export default defineConfig({
|
|
379
|
+
devToolbar: {
|
|
380
|
+
enabled: true,
|
|
381
|
+
panes: [webhooksPane],
|
|
382
|
+
},
|
|
383
|
+
})
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
The endpoint must return JSON with the data array under a key matching the pane `id` (or `dataKey`):
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
// Controller
|
|
390
|
+
async webhooks({ response }: HttpContext) {
|
|
391
|
+
const events = await WebhookEvent.query().orderBy('created_at', 'desc').limit(200)
|
|
392
|
+
return response.json({ webhooks: events })
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
#### `DebugPane` Options
|
|
397
|
+
|
|
398
|
+
| Option | Type | Default | Description |
|
|
399
|
+
|-------------|---------------------|---------|----------------------------------------------|
|
|
400
|
+
| `id` | `string` | -- | Unique identifier (also default data key) |
|
|
401
|
+
| `label` | `string` | -- | Tab display name |
|
|
402
|
+
| `endpoint` | `string` | -- | API endpoint URL |
|
|
403
|
+
| `columns` | `DebugPaneColumn[]` | -- | Column definitions |
|
|
404
|
+
| `search` | `{ placeholder }` | -- | Enable search bar |
|
|
405
|
+
| `dataKey` | `string` | `id` | JSON key for data array (dot notation OK) |
|
|
406
|
+
| `fetchOnce` | `boolean` | `false` | Cache after first fetch |
|
|
407
|
+
| `clearable` | `boolean` | `false` | Show Clear button |
|
|
408
|
+
|
|
409
|
+
#### `DebugPaneColumn` Options
|
|
410
|
+
|
|
411
|
+
| Option | Type | Default | Description |
|
|
412
|
+
|-----------------|--------------------------|----------|------------------------------------------|
|
|
413
|
+
| `key` | `string` | -- | JSON field name |
|
|
414
|
+
| `label` | `string` | -- | Column header text |
|
|
415
|
+
| `width` | `string` | auto | CSS width (e.g. `'60px'`) |
|
|
416
|
+
| `format` | `DebugPaneFormatType` | `'text'` | Cell format (see table below) |
|
|
417
|
+
| `searchable` | `boolean` | `false` | Include in search filtering |
|
|
418
|
+
| `filterable` | `boolean` | `false` | Click to set as search filter |
|
|
419
|
+
| `badgeColorMap` | `Record<string, string>` | -- | Value-to-color map for `badge` format |
|
|
420
|
+
|
|
421
|
+
#### Format Types
|
|
422
|
+
|
|
423
|
+
| Format | Renders As | Expected Input |
|
|
424
|
+
|------------|----------------------------------------|-------------------------|
|
|
425
|
+
| `text` | Escaped plain text | any |
|
|
426
|
+
| `time` | `HH:MM:SS.mmm` | Unix timestamp (ms) |
|
|
427
|
+
| `timeAgo` | `3s ago`, `2m ago` | Unix timestamp (ms) |
|
|
428
|
+
| `duration` | `X.XXms` with color coding | number (ms) |
|
|
429
|
+
| `method` | HTTP method pill badge | `'GET'`, `'POST'`, etc. |
|
|
430
|
+
| `json` | Compact preview, click to expand | object or array |
|
|
431
|
+
| `badge` | Colored pill via `badgeColorMap` | string |
|
|
432
|
+
|
|
433
|
+
Badge colors: `green`, `amber`, `red`, `blue`, `purple`, `muted`
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## Prometheus Integration
|
|
438
|
+
|
|
439
|
+
Export all metrics as Prometheus gauges. Requires `@julr/adonisjs-prometheus`.
|
|
440
|
+
|
|
441
|
+
```ts
|
|
442
|
+
// config/prometheus.ts
|
|
443
|
+
import { defineConfig } from '@julr/adonisjs-prometheus'
|
|
444
|
+
import { httpCollector } from '@julr/adonisjs-prometheus/collectors/http_collector'
|
|
445
|
+
import { serverStatsCollector } from 'adonisjs-server-stats/prometheus'
|
|
446
|
+
|
|
447
|
+
export default defineConfig({
|
|
448
|
+
endpoint: '/metrics',
|
|
449
|
+
collectors: [httpCollector(), serverStatsCollector()],
|
|
450
|
+
})
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Gauges are updated automatically on each collection tick.
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## Log Stream
|
|
458
|
+
|
|
459
|
+
The log stream module watches a JSON log file and broadcasts new entries via Transmit (SSE).
|
|
460
|
+
|
|
461
|
+
**Two purposes:**
|
|
462
|
+
1. Provides error/warning counts to the stats bar via `logCollector()`
|
|
463
|
+
2. Broadcasts individual log entries to a Transmit channel via `LogStreamProvider`
|
|
464
|
+
|
|
465
|
+
Standalone usage:
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
import { LogStreamService } from 'adonisjs-server-stats/log-stream'
|
|
469
|
+
|
|
470
|
+
const service = new LogStreamService('logs/app.log', (entry) => {
|
|
471
|
+
console.log('New log entry:', entry)
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
await service.start()
|
|
475
|
+
// later...
|
|
476
|
+
service.stop()
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## TypeScript
|
|
482
|
+
|
|
483
|
+
All types are exported for consumer use:
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
// Core types
|
|
487
|
+
import type {
|
|
488
|
+
ServerStats,
|
|
489
|
+
ServerStatsConfig,
|
|
490
|
+
MetricCollector,
|
|
491
|
+
MetricValue,
|
|
492
|
+
LogStats,
|
|
493
|
+
DevToolbarOptions,
|
|
494
|
+
} from 'adonisjs-server-stats'
|
|
495
|
+
|
|
496
|
+
// Debug types
|
|
497
|
+
import type {
|
|
498
|
+
DebugPane,
|
|
499
|
+
DebugPaneColumn,
|
|
500
|
+
DebugPaneFormatType,
|
|
501
|
+
DebugPaneSearch,
|
|
502
|
+
BadgeColor,
|
|
503
|
+
QueryRecord,
|
|
504
|
+
EventRecord,
|
|
505
|
+
RouteRecord,
|
|
506
|
+
} from 'adonisjs-server-stats'
|
|
507
|
+
|
|
508
|
+
// Collector option types
|
|
509
|
+
import type {
|
|
510
|
+
HttpCollectorOptions,
|
|
511
|
+
DbPoolCollectorOptions,
|
|
512
|
+
QueueCollectorOptions,
|
|
513
|
+
QueueRedisConnection,
|
|
514
|
+
LogCollectorOptions,
|
|
515
|
+
} from 'adonisjs-server-stats/collectors'
|
|
516
|
+
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## Peer Dependencies
|
|
522
|
+
|
|
523
|
+
All integrations use lazy `import()` -- missing peer deps won't crash the app. The corresponding collector simply returns defaults.
|
|
524
|
+
|
|
525
|
+
| Dependency | Required By |
|
|
526
|
+
|-----------------------------|-----------------------------------|
|
|
527
|
+
| `@adonisjs/core` | Everything (required) |
|
|
528
|
+
| `@adonisjs/lucid` | `dbPoolCollector`, `appCollector` |
|
|
529
|
+
| `@adonisjs/redis` | `redisCollector` |
|
|
530
|
+
| `@adonisjs/transmit` | Provider (SSE broadcast) |
|
|
531
|
+
| `@julr/adonisjs-prometheus` | `serverStatsCollector` |
|
|
532
|
+
| `bullmq` | `queueCollector` |
|
|
533
|
+
| `edge.js` | Edge tag |
|
|
534
|
+
|
|
535
|
+
## License
|
|
536
|
+
|
|
537
|
+
MIT
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { stubsRoot } from './src/stubs/main.js';
|
|
2
|
+
export async function configure(command) {
|
|
3
|
+
const codemods = await command.createCodemods();
|
|
4
|
+
// Publish config file
|
|
5
|
+
await codemods.makeUsingStub(stubsRoot(), 'config.stub', {});
|
|
6
|
+
// Register provider in adonisrc.ts
|
|
7
|
+
await codemods.updateRcFile((rcFile) => {
|
|
8
|
+
rcFile.addProvider('adonisjs-server-stats/provider', ['web']);
|
|
9
|
+
rcFile.addProvider('adonisjs-server-stats/log-stream/provider', ['web']);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MetricCollector } from './collector.js';
|
|
2
|
+
/**
|
|
3
|
+
* Queries application-specific tables for user sessions,
|
|
4
|
+
* pending webhooks, and pending emails.
|
|
5
|
+
*
|
|
6
|
+
* Expects `sessions`, `webhook_events`, and `scheduled_emails` tables
|
|
7
|
+
* to exist. Missing tables are silently ignored (returns 0).
|
|
8
|
+
*
|
|
9
|
+
* **Metrics produced:**
|
|
10
|
+
* - `onlineUsers` -- active session count
|
|
11
|
+
* - `pendingWebhooks` -- webhook events awaiting delivery
|
|
12
|
+
* - `pendingEmails` -- scheduled emails awaiting send
|
|
13
|
+
*
|
|
14
|
+
* **Peer dependencies:** `@adonisjs/lucid`
|
|
15
|
+
*/
|
|
16
|
+
export declare function appCollector(): MetricCollector;
|
|
17
|
+
//# sourceMappingURL=app_collector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app_collector.d.ts","sourceRoot":"","sources":["../../../src/collectors/app_collector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAErD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,IAAI,eAAe,CAuC9C"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Queries application-specific tables for user sessions,
|
|
3
|
+
* pending webhooks, and pending emails.
|
|
4
|
+
*
|
|
5
|
+
* Expects `sessions`, `webhook_events`, and `scheduled_emails` tables
|
|
6
|
+
* to exist. Missing tables are silently ignored (returns 0).
|
|
7
|
+
*
|
|
8
|
+
* **Metrics produced:**
|
|
9
|
+
* - `onlineUsers` -- active session count
|
|
10
|
+
* - `pendingWebhooks` -- webhook events awaiting delivery
|
|
11
|
+
* - `pendingEmails` -- scheduled emails awaiting send
|
|
12
|
+
*
|
|
13
|
+
* **Peer dependencies:** `@adonisjs/lucid`
|
|
14
|
+
*/
|
|
15
|
+
export function appCollector() {
|
|
16
|
+
return {
|
|
17
|
+
name: 'app',
|
|
18
|
+
async collect() {
|
|
19
|
+
try {
|
|
20
|
+
const { default: db } = await import('@adonisjs/lucid/services/db');
|
|
21
|
+
const [sessions, webhooks, emails] = await Promise.all([
|
|
22
|
+
db
|
|
23
|
+
.from('sessions')
|
|
24
|
+
.count('* as total')
|
|
25
|
+
.first()
|
|
26
|
+
.then((r) => Number(r?.total ?? 0)),
|
|
27
|
+
db
|
|
28
|
+
.from('webhook_events')
|
|
29
|
+
.where('status', 'pending')
|
|
30
|
+
.count('* as total')
|
|
31
|
+
.first()
|
|
32
|
+
.then((r) => Number(r?.total ?? 0))
|
|
33
|
+
.catch(() => 0),
|
|
34
|
+
db
|
|
35
|
+
.from('scheduled_emails')
|
|
36
|
+
.where('status', 'pending')
|
|
37
|
+
.count('* as total')
|
|
38
|
+
.first()
|
|
39
|
+
.then((r) => Number(r?.total ?? 0))
|
|
40
|
+
.catch(() => 0),
|
|
41
|
+
]);
|
|
42
|
+
return {
|
|
43
|
+
onlineUsers: sessions,
|
|
44
|
+
pendingWebhooks: webhooks,
|
|
45
|
+
pendingEmails: emails,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return { onlineUsers: 0, pendingWebhooks: 0, pendingEmails: 0 };
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|