adonisjs-server-stats 1.16.1 → 1.18.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/README.md +25 -8
- package/dist/core/core/dashboard-api.d.ts +6 -4
- package/dist/core/core/define-config-helpers.d.ts +1 -0
- package/dist/core/core/types-diagnostics.d.ts +2 -0
- package/dist/core/debug/types.d.ts +2 -0
- package/dist/core/index.js +9 -6
- package/dist/core/types.d.ts +55 -4
- package/dist/react/{CacheSection-DkLtukWz.js → CacheSection-B-lSIN8Q.js} +48 -44
- package/dist/react/{CacheTab-DWeXnEBI.js → CacheTab-BN0QgPXz.js} +1 -1
- package/dist/react/{ConfigSection-DaVbFPDg.js → ConfigSection-u_TSfb9f.js} +1 -1
- package/dist/react/{ConfigTab-D4640WJZ.js → ConfigTab-au26s-ON.js} +1 -1
- package/dist/react/{CustomPaneTab-DglmAVMC.js → CustomPaneTab-qvMnh8ss.js} +1 -1
- package/dist/react/{EmailsSection-vYx6ExTb.js → EmailsSection-HfEMeRex.js} +65 -57
- package/dist/react/{EmailsTab-DKDrRmRH.js → EmailsTab-BWwq-CxB.js} +1 -1
- package/dist/react/{EventsSection-CapH5xut.js → EventsSection-CA9b15dJ.js} +1 -1
- package/dist/react/{EventsTab-BNVEySAm.js → EventsTab-CgSdHbKU.js} +1 -1
- package/dist/react/{InternalsContent-C9lIA92C.js → InternalsContent-CI7HvBtH.js} +51 -47
- package/dist/react/{InternalsSection-lOv_UcSV.js → InternalsSection-IEeWXJqP.js} +1 -1
- package/dist/react/{InternalsTab-BygoSorC.js → InternalsTab-CoVLHQbF.js} +1 -1
- package/dist/react/{JobsSection-ZIQsJWid.js → JobsSection-DgsS_VwU.js} +1 -1
- package/dist/react/{JobsTab-DXQzXrDt.js → JobsTab-CzwLSRUC.js} +1 -1
- package/dist/react/{LogEntryRow-BWkHE51-.js → LogEntryRow-VCCzwD1H.js} +1 -1
- package/dist/react/{LogsSection-ConXdBkL.js → LogsSection-BujNn_cO.js} +2 -2
- package/dist/react/{LogsTab-CJM47LPn.js → LogsTab-BtD1h7DM.js} +2 -2
- package/dist/react/{OverviewSection-DOMu2qvl.js → OverviewSection-B28em0gQ.js} +119 -111
- package/dist/react/{QueriesSection-DFsaOSJI.js → QueriesSection-B0tFn_Dc.js} +1 -1
- package/dist/react/{QueriesTab-CY9CG_7L.js → QueriesTab-DJY7aavR.js} +1 -1
- package/dist/react/{RequestsSection-CC-eVHsl.js → RequestsSection-DPmYM7tq.js} +2 -2
- package/dist/react/{RoutesSection-p1DMq41y.js → RoutesSection-Ck-RcFVS.js} +1 -1
- package/dist/react/{RoutesTab-Cnqy8UcK.js → RoutesTab-Ch3PDLzo.js} +1 -1
- package/dist/react/{SplitPaneWrapper-XgkA0QxE.js → SplitPaneWrapper-BZ6VrBr-.js} +1 -1
- package/dist/react/{TimelineTab-1YOERxe5.js → TimelineTab-_byxh2mJ.js} +2 -2
- package/dist/react/components/Dashboard/overview-data-context.d.ts +15 -0
- package/dist/react/components/Dashboard/sections/OverviewSection.d.ts +9 -0
- package/dist/react/{index-DOSlCpZ9.js → index-De7nsWBN.js} +148 -141
- package/dist/react/index.js +1 -1
- package/dist/src/collectors/app_collector.d.ts +11 -0
- package/dist/src/collectors/app_collector.js +11 -0
- package/dist/src/collectors/redis_collector.js +13 -13
- package/dist/src/dashboard/cache_handlers.d.ts +17 -3
- package/dist/src/dashboard/cache_handlers.js +14 -19
- package/dist/src/dashboard/coalesce_cache.d.ts +2 -8
- package/dist/src/dashboard/coalesce_cache.js +25 -0
- package/dist/src/dashboard/dashboard_controller.d.ts +1 -0
- package/dist/src/dashboard/dashboard_controller.js +8 -3
- package/dist/src/dashboard/dashboard_store.d.ts +0 -1
- package/dist/src/dashboard/dashboard_store.js +18 -18
- package/dist/src/dashboard/filter_handlers.js +26 -4
- package/dist/src/dashboard/flush_manager.d.ts +1 -0
- package/dist/src/dashboard/flush_manager.js +6 -1
- package/dist/src/dashboard/format_helpers.d.ts +0 -4
- package/dist/src/dashboard/format_helpers.js +0 -19
- package/dist/src/dashboard/integrations/config_inspector.js +6 -3
- package/dist/src/dashboard/knex_factory.js +8 -0
- package/dist/src/dashboard/migrator.d.ts +7 -2
- package/dist/src/dashboard/migrator.js +140 -6
- package/dist/src/dashboard/migrator_tables.js +8 -0
- package/dist/src/dashboard/paginate_helper.js +21 -11
- package/dist/src/dashboard/sensitive_patterns.d.ts +37 -0
- package/dist/src/dashboard/sensitive_patterns.js +146 -5
- package/dist/src/dashboard/storage_stats.d.ts +5 -0
- package/dist/src/dashboard/storage_stats.js +15 -1
- package/dist/src/dashboard/write_queue.d.ts +20 -21
- package/dist/src/dashboard/write_queue.js +37 -60
- package/dist/src/debug/query_collector.js +6 -1
- package/dist/src/debug/trace_collector.js +26 -13
- package/dist/src/debug/types.d.ts +2 -0
- package/dist/src/define_config.js +2 -0
- package/dist/src/edge/client/dashboard.js +2 -2
- package/dist/src/edge/client/debug-panel-deferred.js +1 -1
- package/dist/src/edge/client-vue/dashboard.js +5 -5
- package/dist/src/edge/client-vue/debug-panel-deferred.js +2 -2
- package/dist/src/edge/views/dashboard.edge +1 -1
- package/dist/src/edge/views/debug-panel.edge +1 -1
- package/dist/src/edge/views/stats-bar.edge +1 -1
- package/dist/src/middleware/request_tracking_middleware.js +7 -2
- package/dist/src/provider/dashboard_init.d.ts +9 -1
- package/dist/src/provider/dashboard_init.js +10 -0
- package/dist/src/provider/dashboard_setup.js +14 -0
- package/dist/src/provider/diagnostics.d.ts +2 -0
- package/dist/src/provider/diagnostics.js +1 -0
- package/dist/src/provider/email_helpers.js +20 -5
- package/dist/src/provider/server_stats_provider.d.ts +10 -0
- package/dist/src/provider/server_stats_provider.js +26 -7
- package/dist/src/provider/shutdown_helpers.js +3 -1
- package/dist/src/provider/toolbar_setup.js +21 -15
- package/dist/src/stubs/config.stub +4 -0
- package/dist/src/types.d.ts +55 -4
- package/dist/vue/CacheSection-z2TQRez9.js +161 -0
- package/dist/vue/{ConfigSection-BiRAiaHj.js → ConfigSection-CFEaig69.js} +1 -1
- package/dist/vue/{EmailsSection-Dl44qyqY.js → EmailsSection-CYohBo62.js} +89 -85
- package/dist/vue/{EventsSection-CWjeitjU.js → EventsSection-B_TuVrGj.js} +1 -1
- package/dist/vue/{InternalsSection-jhTCuK3j.js → InternalsSection-B1P2kNT-.js} +92 -84
- package/dist/vue/{InternalsTab-Z3c82glB.js → InternalsTab-CRI-IFTo.js} +155 -147
- package/dist/vue/{JobsSection-DOBb4LjZ.js → JobsSection-AjkEzyiv.js} +1 -1
- package/dist/vue/{LogsSection-CXx-HOWJ.js → LogsSection-DZD0CCDi.js} +1 -1
- package/dist/vue/{OverviewSection-CyfNQ8uV.js → OverviewSection-BEc4Gktf.js} +1 -1
- package/dist/vue/{QueriesSection-CXBsFp-y.js → QueriesSection-BMImGXTn.js} +1 -1
- package/dist/vue/{RequestsSection-YTIaZGZd.js → RequestsSection-DOI2MUFX.js} +1 -1
- package/dist/vue/{RoutesSection-CDKMey49.js → RoutesSection-C1sxt-AW.js} +1 -1
- package/dist/vue/composables/useDashboardData.d.ts +1 -1
- package/dist/vue/{index-14x39RI_.js → index-BfPAAjL5.js} +95 -95
- package/dist/vue/index.js +1 -1
- package/package.json +12 -3
- package/dist/src/dashboard/explain_query.d.ts +0 -8
- package/dist/src/dashboard/explain_query.js +0 -22
- package/dist/src/provider/boot_initializer.d.ts +0 -28
- package/dist/src/provider/boot_initializer.js +0 -35
- package/dist/vue/CacheSection-CxEBVVkF.js +0 -156
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* be tested in isolation.
|
|
7
7
|
*/
|
|
8
8
|
import { round } from '../utils/math_helpers.js';
|
|
9
|
-
import {
|
|
9
|
+
import { yieldToEventLoop } from './migrator_tables.js';
|
|
10
|
+
import { sanitizeBindings, sanitizeRecordValues } from './sensitive_patterns.js';
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
11
12
|
// Warn-once tracking for write-path catch blocks
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
@@ -17,6 +18,14 @@ export function hasWarned(path) {
|
|
|
17
18
|
export function markWarned(path) {
|
|
18
19
|
warnedWritePaths.add(path);
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Forget which write paths have warned. Called when a DashboardStore stops so
|
|
23
|
+
* a re-initialized store in the same process warns afresh instead of
|
|
24
|
+
* inheriting a spent latch from its predecessor.
|
|
25
|
+
*/
|
|
26
|
+
export function resetWriteWarnings() {
|
|
27
|
+
warnedWritePaths.clear();
|
|
28
|
+
}
|
|
20
29
|
// ---------------------------------------------------------------------------
|
|
21
30
|
// SQL normalization
|
|
22
31
|
// ---------------------------------------------------------------------------
|
|
@@ -37,65 +46,11 @@ export function normalizeSql(sql) {
|
|
|
37
46
|
.trim();
|
|
38
47
|
}
|
|
39
48
|
// ---------------------------------------------------------------------------
|
|
40
|
-
// Binding hygiene
|
|
49
|
+
// Binding hygiene — implementation lives in sensitive_patterns.ts so the
|
|
50
|
+
// in-memory QueryCollector can sanitize at capture time without importing the
|
|
51
|
+
// write path. Re-exported here for existing consumers.
|
|
41
52
|
// ---------------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
const MAX_BINDING_LEN = 256;
|
|
44
|
-
/** Placeholder stored in place of a binding that looks like a credential. */
|
|
45
|
-
const REDACTED_BINDING = '[redacted]';
|
|
46
|
-
/**
|
|
47
|
-
* Redact and truncate SQL bindings before persistence.
|
|
48
|
-
*
|
|
49
|
-
* Two independent rules, because neither catches everything on its own:
|
|
50
|
-
*
|
|
51
|
-
* 1. **By statement.** If the SQL mentions a credential-shaped identifier
|
|
52
|
-
* (`password`, `remember_token`, `otp`, ...), every binding for that
|
|
53
|
-
* statement is redacted. Positional bindings cannot be mapped back to
|
|
54
|
-
* columns reliably, so this is all-or-nothing per statement — coarse, but
|
|
55
|
-
* it is the only thing that catches a short secret like a 6-digit OTP.
|
|
56
|
-
* 2. **By value shape.** Hashes, JWTs, provider key prefixes, long hex
|
|
57
|
-
* digests, and URLs with embedded credentials are redacted wherever they
|
|
58
|
-
* appear, regardless of the statement.
|
|
59
|
-
*
|
|
60
|
-
* Anything that survives both is truncated at {@link MAX_BINDING_LEN} so an
|
|
61
|
-
* oversized payload cannot bloat the row.
|
|
62
|
-
*
|
|
63
|
-
* Ordinary parameters — ids, flags, emails, timestamps — still pass through, so
|
|
64
|
-
* the query pane stays useful for debugging.
|
|
65
|
-
*/
|
|
66
|
-
export function sanitizeBindings(bindings, sqlText) {
|
|
67
|
-
const redactAll = sqlText !== undefined && sqlMentionsSecret(sqlText);
|
|
68
|
-
return sanitizeBindingValue(bindings, redactAll);
|
|
69
|
-
}
|
|
70
|
-
function sanitizeBindingValue(value, redactAll) {
|
|
71
|
-
if (Array.isArray(value))
|
|
72
|
-
return value.map((v) => sanitizeBindingValue(v, redactAll));
|
|
73
|
-
if (value !== null && typeof value === 'object')
|
|
74
|
-
return sanitizeNamedBindings(value, redactAll);
|
|
75
|
-
if (typeof value === 'string')
|
|
76
|
-
return sanitizeStringBinding(value, redactAll);
|
|
77
|
-
// A statement touching a secret column may bind it as a non-string — a numeric
|
|
78
|
-
// OTP, for instance — so redact those too rather than only masking strings.
|
|
79
|
-
// Booleans and null carry nothing worth hiding.
|
|
80
|
-
const redactable = value !== null && value !== undefined && typeof value !== 'boolean';
|
|
81
|
-
return redactAll && redactable ? REDACTED_BINDING : value;
|
|
82
|
-
}
|
|
83
|
-
/** Named bindings carry their own key, so use it when it is telling. */
|
|
84
|
-
function sanitizeNamedBindings(value, redactAll) {
|
|
85
|
-
const out = {};
|
|
86
|
-
for (const [key, nested] of Object.entries(value)) {
|
|
87
|
-
out[key] = sanitizeBindingValue(nested, redactAll || isSecretName(key));
|
|
88
|
-
}
|
|
89
|
-
return out;
|
|
90
|
-
}
|
|
91
|
-
function sanitizeStringBinding(value, redactAll) {
|
|
92
|
-
if (redactAll || looksLikeCredentialValue(value))
|
|
93
|
-
return REDACTED_BINDING;
|
|
94
|
-
if (value.length > MAX_BINDING_LEN) {
|
|
95
|
-
return value.slice(0, MAX_BINDING_LEN) + `…[truncated ${value.length} chars]`;
|
|
96
|
-
}
|
|
97
|
-
return value;
|
|
98
|
-
}
|
|
53
|
+
export { sanitizeBindings };
|
|
99
54
|
// ---------------------------------------------------------------------------
|
|
100
55
|
// Pure data-prep functions
|
|
101
56
|
// ---------------------------------------------------------------------------
|
|
@@ -135,6 +90,13 @@ export function prepareRequestRows(requests) {
|
|
|
135
90
|
}
|
|
136
91
|
/**
|
|
137
92
|
* Transform raw log entries into SQLite-ready row objects.
|
|
93
|
+
*
|
|
94
|
+
* The full pino record lands in `data`, and log lines routinely carry bound
|
|
95
|
+
* request context — authorization headers, cookies, tokens in payloads — so
|
|
96
|
+
* the record is run through the shared redaction rules (secret-named keys and
|
|
97
|
+
* credential-shaped values) before it is stringified. The `message` column is
|
|
98
|
+
* kept verbatim: it is free text, and the shape rules are anchored to whole
|
|
99
|
+
* values, so prose stays readable.
|
|
138
100
|
*/
|
|
139
101
|
export function prepareLogRows(logs) {
|
|
140
102
|
return logs.map((entry) => {
|
|
@@ -145,7 +107,7 @@ export function prepareLogRows(logs) {
|
|
|
145
107
|
request_id: entry.request_id || entry.requestId || entry['x-request-id']
|
|
146
108
|
? String(entry.request_id || entry.requestId || entry['x-request-id'])
|
|
147
109
|
: null,
|
|
148
|
-
data: JSON.stringify(entry),
|
|
110
|
+
data: JSON.stringify(sanitizeRecordValues(entry)),
|
|
149
111
|
};
|
|
150
112
|
});
|
|
151
113
|
}
|
|
@@ -187,9 +149,16 @@ export function buildEventRows(events) {
|
|
|
187
149
|
// ---------------------------------------------------------------------------
|
|
188
150
|
/**
|
|
189
151
|
* Insert rows into a table in batches of 50.
|
|
152
|
+
*
|
|
153
|
+
* Yields to the event loop between chunks: better-sqlite3 is fully
|
|
154
|
+
* synchronous, so without a real macrotask hop a large batch would execute
|
|
155
|
+
* back-to-back on microtask continuations and stall the host app — the exact
|
|
156
|
+
* failure mode `migrator_tables.ts` documents and guards against.
|
|
190
157
|
*/
|
|
191
158
|
export async function batchInsert(trx, table, rows) {
|
|
192
159
|
for (let i = 0; i < rows.length; i += 50) {
|
|
160
|
+
if (i > 0)
|
|
161
|
+
await yieldToEventLoop();
|
|
193
162
|
await trx(table).insert(rows.slice(i, i + 50));
|
|
194
163
|
}
|
|
195
164
|
}
|
|
@@ -233,7 +202,15 @@ async function insertOneRequest(trx, prepared) {
|
|
|
233
202
|
}
|
|
234
203
|
}
|
|
235
204
|
export async function flushRequests(trx, preparedRequests) {
|
|
205
|
+
let sinceYield = 0;
|
|
236
206
|
for (const prepared of preparedRequests) {
|
|
207
|
+
// Up to ~4 synchronous statements per request; a full backlog is hundreds
|
|
208
|
+
// of them in one flush. Yield periodically so the host app's I/O keeps
|
|
209
|
+
// being serviced mid-flush (see batchInsert for why awaits alone don't).
|
|
210
|
+
if (++sinceYield >= 10) {
|
|
211
|
+
sinceYield = 0;
|
|
212
|
+
await yieldToEventLoop();
|
|
213
|
+
}
|
|
237
214
|
try {
|
|
238
215
|
await insertOneRequest(trx, prepared);
|
|
239
216
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sanitizeBindings } from '../dashboard/sensitive_patterns.js';
|
|
1
2
|
import { isExcludedRequest } from '../middleware/request_tracking_middleware.js';
|
|
2
3
|
import { round } from '../utils/math_helpers.js';
|
|
3
4
|
import { RingBuffer } from './ring_buffer.js';
|
|
@@ -14,7 +15,11 @@ function buildQueryRecord(data, id) {
|
|
|
14
15
|
return {
|
|
15
16
|
id,
|
|
16
17
|
sql: data.sql || '',
|
|
17
|
-
|
|
18
|
+
// Sanitized at capture time so the in-memory debug panel never shows a
|
|
19
|
+
// credential the persisted dashboard would have redacted. The write path
|
|
20
|
+
// sanitizes again (idempotently) as defense in depth for records loaded
|
|
21
|
+
// from an older on-disk dump.
|
|
22
|
+
bindings: sanitizeBindings(data.bindings || [], data.sql || '') ?? [],
|
|
18
23
|
duration: round(parseDuration(data.duration)),
|
|
19
24
|
method: data.method || 'unknown',
|
|
20
25
|
model: data.model || null,
|
|
@@ -7,11 +7,16 @@ import { RingBuffer } from './ring_buffer.js';
|
|
|
7
7
|
*/
|
|
8
8
|
const globalRef = { current: null };
|
|
9
9
|
/**
|
|
10
|
-
* Tracks
|
|
11
|
-
* second instance
|
|
12
|
-
*
|
|
10
|
+
* Tracks which TraceCollector currently owns the console.warn patch, so a
|
|
11
|
+
* second instance cannot double-wrap it and leak the patch permanently. The
|
|
12
|
+
* owner (not a boolean) matters: if a prior instance never ran stop(), its
|
|
13
|
+
* wrapper checks the *old* AsyncLocalStorage and silently stops attaching
|
|
14
|
+
* warnings to traces — a newcomer must be able to detect that and take over.
|
|
13
15
|
*/
|
|
14
|
-
const consoleWarnPatch = {
|
|
16
|
+
const consoleWarnPatch = {
|
|
17
|
+
owner: null,
|
|
18
|
+
original: null,
|
|
19
|
+
};
|
|
15
20
|
/**
|
|
16
21
|
* Wrap an async function in a traced span.
|
|
17
22
|
*
|
|
@@ -157,12 +162,18 @@ export class TraceCollector {
|
|
|
157
162
|
};
|
|
158
163
|
emitter.on('db:query', this.dbHandler);
|
|
159
164
|
}
|
|
160
|
-
// Intercept console.warn to capture warnings per-request.
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
// Intercept console.warn to capture warnings per-request. If a prior
|
|
166
|
+
// instance still owns the patch (its stop() was skipped), restore its
|
|
167
|
+
// original first and take over — deferring to the stale owner would leave
|
|
168
|
+
// a wrapper bound to a dead AsyncLocalStorage, silently detaching
|
|
169
|
+
// warnings from every trace. Never wrap the wrapper: the restore below
|
|
170
|
+
// guarantees `console.warn` is the real one before we capture it.
|
|
171
|
+
if (consoleWarnPatch.owner !== this) {
|
|
172
|
+
if (consoleWarnPatch.owner && consoleWarnPatch.original) {
|
|
173
|
+
console.warn = consoleWarnPatch.original;
|
|
174
|
+
}
|
|
175
|
+
consoleWarnPatch.owner = this;
|
|
176
|
+
consoleWarnPatch.original = console.warn;
|
|
166
177
|
this.originalConsoleWarn = console.warn;
|
|
167
178
|
console.warn = (...args) => {
|
|
168
179
|
const ctx = this.als.getStore();
|
|
@@ -178,10 +189,12 @@ export class TraceCollector {
|
|
|
178
189
|
if (this.emitter && this.dbHandler) {
|
|
179
190
|
this.emitter.off('db:query', this.dbHandler);
|
|
180
191
|
}
|
|
181
|
-
// Only restore console.warn if this instance
|
|
182
|
-
|
|
192
|
+
// Only restore console.warn if this instance still owns the patch — a
|
|
193
|
+
// newer instance may have taken over, and restoring would clobber it.
|
|
194
|
+
if (consoleWarnPatch.owner === this && this.originalConsoleWarn) {
|
|
183
195
|
console.warn = this.originalConsoleWarn;
|
|
184
|
-
consoleWarnPatch.
|
|
196
|
+
consoleWarnPatch.owner = null;
|
|
197
|
+
consoleWarnPatch.original = null;
|
|
185
198
|
}
|
|
186
199
|
this.dbHandler = null;
|
|
187
200
|
this.emitter = null;
|
|
@@ -284,6 +284,8 @@ export interface DevToolbarConfig {
|
|
|
284
284
|
dashboardPath: string;
|
|
285
285
|
/** Data retention period in days for historical persistence. */
|
|
286
286
|
retentionDays: number;
|
|
287
|
+
/** Size cap in MB for the SQLite database's live data; `0` disables it. */
|
|
288
|
+
maxDbSizeMb: number;
|
|
287
289
|
/** Path to the SQLite database file for historical persistence. */
|
|
288
290
|
dbPath: string;
|
|
289
291
|
/** Base path for the debug toolbar API endpoints. */
|
|
@@ -33,6 +33,8 @@ function applyDashboardConfig(dashboard, result) {
|
|
|
33
33
|
result.dashboardPath = dashboard.path;
|
|
34
34
|
if (dashboard.retentionDays !== undefined)
|
|
35
35
|
result.retentionDays = dashboard.retentionDays;
|
|
36
|
+
if (dashboard.maxDbSizeMb !== undefined)
|
|
37
|
+
result.maxDbSizeMb = dashboard.maxDbSizeMb;
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
/** Advanced config field mappings. */
|