adonisjs-server-stats 1.17.0 → 1.18.1
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 +11 -6
- 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/types.d.ts +31 -3
- package/dist/react/{CacheSection-C-X2Tnca.js → CacheSection-B-lSIN8Q.js} +1 -1
- package/dist/react/{CacheTab-D55mfmeE.js → CacheTab-BN0QgPXz.js} +1 -1
- package/dist/react/{ConfigSection-C-HGnZfd.js → ConfigSection-u_TSfb9f.js} +1 -1
- package/dist/react/{ConfigTab-CNnsOXBh.js → ConfigTab-au26s-ON.js} +1 -1
- package/dist/react/{CustomPaneTab-D-267UVd.js → CustomPaneTab-qvMnh8ss.js} +1 -1
- package/dist/react/{EmailsSection-Bqw4DI4r.js → EmailsSection-HfEMeRex.js} +1 -1
- package/dist/react/{EmailsTab-CkHOhj2F.js → EmailsTab-BWwq-CxB.js} +1 -1
- package/dist/react/{EventsSection-CMeyjzVC.js → EventsSection-CA9b15dJ.js} +1 -1
- package/dist/react/{EventsTab-DgS2v1HX.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-oWH6pbrG.js → JobsSection-DgsS_VwU.js} +1 -1
- package/dist/react/{JobsTab-zdxX_as-.js → JobsTab-CzwLSRUC.js} +1 -1
- package/dist/react/{LogEntryRow-Cn8Ee_X0.js → LogEntryRow-VCCzwD1H.js} +1 -1
- package/dist/react/{LogsSection-XmiI2MMs.js → LogsSection-BujNn_cO.js} +2 -2
- package/dist/react/{LogsTab-ixusY4v7.js → LogsTab-BtD1h7DM.js} +2 -2
- package/dist/react/{OverviewSection-CvhObTLP.js → OverviewSection-B28em0gQ.js} +1 -1
- package/dist/react/{QueriesSection-BvledwBI.js → QueriesSection-B0tFn_Dc.js} +1 -1
- package/dist/react/{QueriesTab-smQQJvJa.js → QueriesTab-DJY7aavR.js} +1 -1
- package/dist/react/{RequestsSection-DKuYpViv.js → RequestsSection-DPmYM7tq.js} +2 -2
- package/dist/react/{RoutesSection-5RNVurQ6.js → RoutesSection-Ck-RcFVS.js} +1 -1
- package/dist/react/{RoutesTab-CpXNO-Rx.js → RoutesTab-Ch3PDLzo.js} +1 -1
- package/dist/react/{SplitPaneWrapper-DQvJnsuk.js → SplitPaneWrapper-BZ6VrBr-.js} +1 -1
- package/dist/react/{TimelineTab-LHQHHKkO.js → TimelineTab-_byxh2mJ.js} +2 -2
- package/dist/react/{index-LlFvooUN.js → index-De7nsWBN.js} +2 -2
- package/dist/react/index.js +1 -1
- package/dist/src/dashboard/dashboard_store.js +4 -12
- 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 +4 -0
- package/dist/src/dashboard/storage_stats.d.ts +5 -0
- package/dist/src/dashboard/storage_stats.js +15 -1
- 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 +3 -3
- package/dist/src/edge/client-vue/debug-panel-deferred.js +2 -2
- package/dist/src/log_stream/log_stream_service.d.ts +5 -0
- package/dist/src/log_stream/log_stream_service.js +35 -10
- 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/server_stats_provider.d.ts +1 -0
- package/dist/src/stubs/config.stub +4 -0
- package/dist/src/types.d.ts +31 -3
- package/dist/vue/{CacheSection-Bm0goqX6.js → CacheSection-z2TQRez9.js} +1 -1
- package/dist/vue/{ConfigSection-BrYslIMN.js → ConfigSection-CFEaig69.js} +1 -1
- package/dist/vue/{EmailsSection-B8N5vaWA.js → EmailsSection-CYohBo62.js} +1 -1
- package/dist/vue/{EventsSection-D2Y_xt0m.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-cEKqELLC.js → JobsSection-AjkEzyiv.js} +1 -1
- package/dist/vue/{LogsSection-C3H2c0S3.js → LogsSection-DZD0CCDi.js} +1 -1
- package/dist/vue/{OverviewSection-I2swuHDa.js → OverviewSection-BEc4Gktf.js} +1 -1
- package/dist/vue/{QueriesSection-CF1IE3SN.js → QueriesSection-BMImGXTn.js} +1 -1
- package/dist/vue/{RequestsSection-CuwgXzmU.js → RequestsSection-DOI2MUFX.js} +1 -1
- package/dist/vue/{RoutesSection-BhJCF7Rn.js → RoutesSection-C1sxt-AW.js} +1 -1
- package/dist/vue/{index-DUsImuIN.js → index-BfPAAjL5.js} +2 -2
- package/dist/vue/index.js +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ export declare function parseAndEnrich(line: string): Record<string, unknown> |
|
|
|
3
3
|
export declare class LogStreamService {
|
|
4
4
|
private recentEntries;
|
|
5
5
|
private static readonly MAX_RECENT_ENTRIES;
|
|
6
|
+
private static readonly MAX_POLL_BYTES;
|
|
6
7
|
private lastSize;
|
|
7
8
|
private intervalId;
|
|
8
9
|
private logPath;
|
|
@@ -16,8 +17,12 @@ export declare class LogStreamService {
|
|
|
16
17
|
* in real-time without file polling.
|
|
17
18
|
*/
|
|
18
19
|
ingest(entry: Record<string, unknown>): void;
|
|
20
|
+
/** Record a timestamp, capping the array to prevent unbounded growth under high log volume. */
|
|
21
|
+
private pushRecent;
|
|
19
22
|
getLogStats(): LogStats;
|
|
20
23
|
start(): Promise<void>;
|
|
21
24
|
stop(): void;
|
|
25
|
+
/** Reset the once-per-streak failure flag after a fully successful poll. */
|
|
26
|
+
private markPollHealthy;
|
|
22
27
|
private pollNewEntries;
|
|
23
28
|
}
|
|
@@ -27,6 +27,7 @@ export function parseAndEnrich(line) {
|
|
|
27
27
|
export class LogStreamService {
|
|
28
28
|
recentEntries = [];
|
|
29
29
|
static MAX_RECENT_ENTRIES = 10_000;
|
|
30
|
+
static MAX_POLL_BYTES = 4 * 1024 * 1024;
|
|
30
31
|
lastSize = 0;
|
|
31
32
|
intervalId = null;
|
|
32
33
|
logPath;
|
|
@@ -44,12 +45,15 @@ export class LogStreamService {
|
|
|
44
45
|
*/
|
|
45
46
|
ingest(entry) {
|
|
46
47
|
const level = typeof entry.level === 'number' ? entry.level : 30;
|
|
47
|
-
|
|
48
|
+
this.pushRecent(Date.now(), level);
|
|
49
|
+
this.onEntry?.(entry);
|
|
50
|
+
}
|
|
51
|
+
/** Record a timestamp, capping the array to prevent unbounded growth under high log volume. */
|
|
52
|
+
pushRecent(time, level) {
|
|
48
53
|
if (this.recentEntries.length >= LogStreamService.MAX_RECENT_ENTRIES) {
|
|
49
54
|
this.recentEntries.splice(0, Math.floor(LogStreamService.MAX_RECENT_ENTRIES / 4));
|
|
50
55
|
}
|
|
51
|
-
this.recentEntries.push({ time
|
|
52
|
-
this.onEntry?.(entry);
|
|
56
|
+
this.recentEntries.push({ time, level });
|
|
53
57
|
}
|
|
54
58
|
getLogStats() {
|
|
55
59
|
const now = Date.now();
|
|
@@ -102,36 +106,57 @@ export class LogStreamService {
|
|
|
102
106
|
this.intervalId = null;
|
|
103
107
|
}
|
|
104
108
|
}
|
|
109
|
+
/** Reset the once-per-streak failure flag after a fully successful poll. */
|
|
110
|
+
markPollHealthy() {
|
|
111
|
+
if (this.warnedPollFailure) {
|
|
112
|
+
this.warnedPollFailure = false;
|
|
113
|
+
log.info('log stream: log file is readable again — resuming');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
105
116
|
async pollNewEntries() {
|
|
106
117
|
if (!this.logPath)
|
|
107
118
|
return;
|
|
108
119
|
try {
|
|
109
|
-
this.warnedPollFailure = false;
|
|
110
120
|
const stats = await stat(this.logPath);
|
|
111
121
|
// File was truncated/rotated — reset
|
|
112
122
|
if (stats.size < this.lastSize) {
|
|
113
123
|
this.lastSize = 0;
|
|
114
124
|
}
|
|
115
|
-
if (stats.size <= this.lastSize)
|
|
125
|
+
if (stats.size <= this.lastSize) {
|
|
126
|
+
this.markPollHealthy();
|
|
116
127
|
return;
|
|
117
|
-
|
|
128
|
+
}
|
|
129
|
+
// Cap each read so a rotation reset or burst can never allocate the
|
|
130
|
+
// whole backlog in one buffer; skip ahead and read only the tail.
|
|
131
|
+
// A partial first line after skipping fails JSON.parse and is dropped.
|
|
132
|
+
const readFrom = Math.max(this.lastSize, stats.size - LogStreamService.MAX_POLL_BYTES);
|
|
133
|
+
const newBytes = stats.size - readFrom;
|
|
118
134
|
const buffer = Buffer.alloc(newBytes);
|
|
119
135
|
const fd = await open(this.logPath, 'r');
|
|
120
|
-
await fd.read(buffer, 0, newBytes,
|
|
136
|
+
await fd.read(buffer, 0, newBytes, readFrom).finally(() => fd.close());
|
|
121
137
|
this.lastSize = stats.size;
|
|
138
|
+
this.markPollHealthy();
|
|
122
139
|
for (const line of buffer.toString('utf-8').trim().split('\n')) {
|
|
123
140
|
const entry = parseAndEnrich(line);
|
|
124
141
|
if (entry) {
|
|
125
142
|
const level = typeof entry.level === 'number' ? entry.level : 30;
|
|
126
143
|
const time = typeof entry.time === 'number' ? entry.time : Date.now();
|
|
127
|
-
this.
|
|
144
|
+
this.pushRecent(time, level);
|
|
128
145
|
this.onEntry?.(entry);
|
|
129
146
|
}
|
|
130
147
|
}
|
|
131
148
|
}
|
|
132
149
|
catch (err) {
|
|
133
|
-
if (
|
|
134
|
-
|
|
150
|
+
if (this.warnedPollFailure)
|
|
151
|
+
return;
|
|
152
|
+
this.warnedPollFailure = true;
|
|
153
|
+
// A missing file is a normal state for the fallback poller — the file
|
|
154
|
+
// appears once the app writes its first log line. Anything else
|
|
155
|
+
// (permissions, a directory in the way) deserves a real warning.
|
|
156
|
+
if (err?.code === 'ENOENT') {
|
|
157
|
+
log.info('log stream: log file not found (will keep watching) — ' + this.logPath);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
135
160
|
log.warn('log stream: cannot read log file — ' + err?.message);
|
|
136
161
|
}
|
|
137
162
|
}
|
|
@@ -62,6 +62,7 @@ const TOOLBAR_DEFAULTS = {
|
|
|
62
62
|
dashboard: false,
|
|
63
63
|
dashboardPath: '/__stats',
|
|
64
64
|
retentionDays: 7,
|
|
65
|
+
maxDbSizeMb: 500,
|
|
65
66
|
dbPath: '.adonisjs/server-stats/dashboard.sqlite3',
|
|
66
67
|
debugEndpoint: '/admin/api/debug',
|
|
67
68
|
};
|
|
@@ -110,6 +111,18 @@ function resolveRetentionDays(ctx, explicit) {
|
|
|
110
111
|
return explicit;
|
|
111
112
|
return ctx?.inProduction ? PRODUCTION_RETENTION_DAYS : TOOLBAR_DEFAULTS.retentionDays;
|
|
112
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Resolve the database size cap. Order: `production.maxDbSizeMb` when in
|
|
116
|
+
* production, then whatever `dashboard` set, then the 500 MB default.
|
|
117
|
+
*/
|
|
118
|
+
function resolveMaxDbSizeMb(ctx, explicit) {
|
|
119
|
+
const fromProduction = ctx?.inProduction ? ctx.production?.maxDbSizeMb : undefined;
|
|
120
|
+
if (fromProduction !== undefined)
|
|
121
|
+
return fromProduction;
|
|
122
|
+
if (explicit !== undefined)
|
|
123
|
+
return explicit;
|
|
124
|
+
return TOOLBAR_DEFAULTS.maxDbSizeMb;
|
|
125
|
+
}
|
|
113
126
|
/**
|
|
114
127
|
* Resolve a partial DevToolbarConfig by filling in all defaults.
|
|
115
128
|
*
|
|
@@ -125,6 +138,7 @@ export function resolveToolbarConfig(partial, ctx) {
|
|
|
125
138
|
return {
|
|
126
139
|
...merged,
|
|
127
140
|
retentionDays: resolveRetentionDays(ctx, partial.retentionDays),
|
|
141
|
+
maxDbSizeMb: resolveMaxDbSizeMb(ctx, partial.maxDbSizeMb),
|
|
128
142
|
capture: resolveCapture(ctx, merged.tracing),
|
|
129
143
|
};
|
|
130
144
|
}
|
|
@@ -47,6 +47,7 @@ interface DiagnosticsToolbar {
|
|
|
47
47
|
dashboard?: boolean;
|
|
48
48
|
dashboardPath?: string;
|
|
49
49
|
retentionDays?: number;
|
|
50
|
+
maxDbSizeMb?: number;
|
|
50
51
|
dbPath?: string;
|
|
51
52
|
persistDebugData?: boolean | string;
|
|
52
53
|
debugEndpoint?: string;
|
|
@@ -118,6 +119,7 @@ export declare function buildDiagnostics(input: DiagnosticsInput): {
|
|
|
118
119
|
tracing: boolean;
|
|
119
120
|
dashboard: boolean;
|
|
120
121
|
retentionDays: number;
|
|
122
|
+
maxDbSizeMb: number;
|
|
121
123
|
persistDebugData: string | boolean;
|
|
122
124
|
renderer: string;
|
|
123
125
|
excludeFromTracing: string[];
|
|
@@ -85,6 +85,7 @@ function pickToolbarFeatures(tb) {
|
|
|
85
85
|
tracing: tb.tracing ?? true,
|
|
86
86
|
dashboard: tb.dashboard ?? false,
|
|
87
87
|
retentionDays: tb.retentionDays ?? 7,
|
|
88
|
+
maxDbSizeMb: tb.maxDbSizeMb ?? 500,
|
|
88
89
|
persistDebugData: tb.persistDebugData ?? false,
|
|
89
90
|
renderer: tb.renderer ?? 'preact',
|
|
90
91
|
excludeFromTracing: tb.excludeFromTracing ?? [],
|
|
@@ -17,6 +17,10 @@ export default defineConfig({
|
|
|
17
17
|
// Enable the full-page dashboard at /__stats
|
|
18
18
|
// dashboard: true,
|
|
19
19
|
|
|
20
|
+
// History window and disk budget for persisted stats. When the database
|
|
21
|
+
// outgrows the size cap the oldest records are pruned regardless of age.
|
|
22
|
+
// dashboard: { retentionDays: 7, maxDbSizeMb: 500 },
|
|
23
|
+
|
|
20
24
|
// Log detailed initialization steps (SQLite, migrations, routes, etc.)
|
|
21
25
|
// verbose: true,
|
|
22
26
|
|
package/dist/src/types.d.ts
CHANGED
|
@@ -263,6 +263,15 @@ export interface DevToolbarOptions {
|
|
|
263
263
|
* @default 7
|
|
264
264
|
*/
|
|
265
265
|
retentionDays?: number;
|
|
266
|
+
/**
|
|
267
|
+
* Size cap in MB for the SQLite database's live data. When exceeded,
|
|
268
|
+
* the hourly cleanup deletes the oldest records — regardless of age —
|
|
269
|
+
* until usage drops below the cap, effectively shortening the retention
|
|
270
|
+
* window when write volume outruns `retentionDays`. Set to `0` to
|
|
271
|
+
* disable the cap.
|
|
272
|
+
* @default 500
|
|
273
|
+
*/
|
|
274
|
+
maxDbSizeMb?: number;
|
|
266
275
|
/**
|
|
267
276
|
* Path to the SQLite database file for historical persistence.
|
|
268
277
|
* Relative to app root.
|
|
@@ -434,6 +443,17 @@ export interface DashboardConfig {
|
|
|
434
443
|
* @default 7
|
|
435
444
|
*/
|
|
436
445
|
retentionDays?: number;
|
|
446
|
+
/**
|
|
447
|
+
* Size cap in MB for the SQLite database's live data.
|
|
448
|
+
*
|
|
449
|
+
* When exceeded, the cleanup deletes the oldest records — regardless
|
|
450
|
+
* of age — until usage drops below the cap. This bounds disk usage
|
|
451
|
+
* when write volume (tracing, verbose query logging) outruns what
|
|
452
|
+
* `retentionDays` alone would keep small. Set to `0` to disable.
|
|
453
|
+
*
|
|
454
|
+
* @default 500
|
|
455
|
+
*/
|
|
456
|
+
maxDbSizeMb?: number;
|
|
437
457
|
}
|
|
438
458
|
/**
|
|
439
459
|
* Access-control callback signature.
|
|
@@ -515,13 +535,21 @@ export interface ProductionConfig {
|
|
|
515
535
|
capture?: CaptureConfig;
|
|
516
536
|
/**
|
|
517
537
|
* How many days of history to keep in SQLite, overriding the usual default
|
|
518
|
-
* of 7. Retention deletes rows hourly
|
|
519
|
-
*
|
|
520
|
-
* the dashboard's storage panel.
|
|
538
|
+
* of 7. Retention deletes rows hourly and returns freed pages to the OS
|
|
539
|
+
* incrementally — watch actual size via the dashboard's storage panel.
|
|
521
540
|
*
|
|
522
541
|
* @default 3
|
|
523
542
|
*/
|
|
524
543
|
retentionDays?: number;
|
|
544
|
+
/**
|
|
545
|
+
* Size cap in MB for the SQLite database's live data in production,
|
|
546
|
+
* overriding the usual default. When exceeded, the hourly cleanup deletes
|
|
547
|
+
* the oldest records — regardless of age — until usage drops below the
|
|
548
|
+
* cap. Set to `0` to disable.
|
|
549
|
+
*
|
|
550
|
+
* @default 500
|
|
551
|
+
*/
|
|
552
|
+
maxDbSizeMb?: number;
|
|
525
553
|
}
|
|
526
554
|
/**
|
|
527
555
|
* Advanced options that most users never need to touch.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineComponent as L, inject as y, ref as h, onMounted as x, computed as w, onUnmounted as F, openBlock as l, createElementBlock as n, createElementVNode as e, toDisplayString as c, createCommentVNode as z, createVNode as N, unref as p, Fragment as U, renderList as B, withModifiers as M, createBlock as H } from "vue";
|
|
2
|
-
import { u as I } from "./index-
|
|
2
|
+
import { u as I } from "./index-BfPAAjL5.js";
|
|
3
3
|
import { u as j } from "./useResizableTable-BoivAevK.js";
|
|
4
4
|
import { DashboardApi as q, formatCacheSize as P, formatTtl as G } from "adonisjs-server-stats/core";
|
|
5
5
|
import { u as J } from "./useApiClient-BQQ9CF-q.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineComponent as nt, inject as L, ref as h, computed as k, openBlock as r, createElementBlock as c, createElementVNode as n, normalizeClass as a, createCommentVNode as g, Fragment as x, toDisplayString as u, unref as d, renderList as E, normalizeStyle as j } from "vue";
|
|
2
|
-
import { u as lt } from "./index-
|
|
2
|
+
import { u as lt } from "./index-BfPAAjL5.js";
|
|
3
3
|
import { isRedactedValue as p, flattenConfig as O, countLeaves as ot, collectTopLevelObjectKeys as st, copyWithFeedback as at, formatFlatValue as rt } from "adonisjs-server-stats/core";
|
|
4
4
|
const ct = { style: { position: "relative", flex: 1 } }, ut = ["value"], it = ["onClick"], pt = { key: 0 }, dt = ["onClick"], vt = { key: 0 }, ft = { style: { padding: "4px 16px", fontSize: "10px", color: "var(--ss-muted)" } }, yt = ["onClick"], gt = ["title"], bt = ["title"], ht = ["onClick"], $t = {
|
|
5
5
|
key: 1,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineComponent as B, inject as f, ref as w, computed as g, onUnmounted as D, openBlock as s, createElementBlock as n, createElementVNode as t, Fragment as m, createTextVNode as d, toDisplayString as i, createCommentVNode as _, normalizeClass as k, createVNode as R, unref as r, renderList as U, createBlock as z } from "vue";
|
|
2
2
|
import { formatTime as L, timeAgo as M } from "adonisjs-server-stats/core";
|
|
3
|
-
import { u as q } from "./index-
|
|
3
|
+
import { u as q } from "./index-BfPAAjL5.js";
|
|
4
4
|
import { u as H } from "./useResizableTable-BoivAevK.js";
|
|
5
5
|
import { _ as I } from "./FilterBar.vue_vue_type_script_setup_true_lang-ClJ37hhT.js";
|
|
6
6
|
import { _ as K } from "./PaginationControls.vue_vue_type_script_setup_true_lang-CuN7g_8Z.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineComponent as E, inject as i, ref as p, computed as P, openBlock as s, createElementBlock as l, createVNode as v, unref as a, Fragment as h, createElementVNode as e, renderList as T, toDisplayString as c, createBlock as C, createCommentVNode as D } from "vue";
|
|
2
2
|
import { formatTime as S, timeAgo as V } from "adonisjs-server-stats/core";
|
|
3
|
-
import { u as $ } from "./index-
|
|
3
|
+
import { u as $ } from "./index-BfPAAjL5.js";
|
|
4
4
|
import { u as A } from "./useResizableTable-BoivAevK.js";
|
|
5
5
|
import { _ as B } from "./FilterBar.vue_vue_type_script_setup_true_lang-ClJ37hhT.js";
|
|
6
6
|
import { _ as R } from "./PaginationControls.vue_vue_type_script_setup_true_lang-CuN7g_8Z.js";
|