adonisjs-server-stats 1.10.0 → 1.10.3
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 +23 -14
- package/dist/core/config-utils.d.ts +8 -0
- package/dist/core/constants.d.ts +4 -0
- package/dist/core/dashboard-data-controller.d.ts +16 -0
- package/dist/core/dashboard-data-helpers.d.ts +12 -0
- package/dist/core/debug-data-controller.d.ts +4 -0
- package/dist/core/define-config-helpers.d.ts +25 -0
- package/dist/core/feature-detect-helpers.d.ts +36 -0
- package/dist/core/formatters-helpers.d.ts +23 -0
- package/dist/core/index.js +594 -509
- package/dist/core/log-utils-helpers.d.ts +13 -0
- package/dist/core/metrics.d.ts +3 -28
- package/dist/core/pagination.d.ts +0 -9
- package/dist/core/server-stats-controller.d.ts +6 -0
- package/dist/core/transmit-helpers.d.ts +7 -0
- package/dist/core/types-dashboard.d.ts +178 -0
- package/dist/core/types-diagnostics.d.ts +85 -0
- package/dist/core/types.d.ts +10 -442
- package/dist/react/{CacheSection-UCMptWyn.js → CacheSection-baMZotSn.js} +2 -2
- package/dist/react/CacheTab-2cw_rMzj.js +117 -0
- package/dist/react/{ConfigSection-DfFd-WRq.js → ConfigSection-DGgqjAal.js} +1 -1
- package/dist/react/{ConfigTab-Bdg8YMer.js → ConfigTab-H3OnYqmK.js} +1 -1
- package/dist/react/CustomPaneTab-B6r7ha0u.js +98 -0
- package/dist/react/{EmailsSection-CM7stSyh.js → EmailsSection-C-UZISG-.js} +2 -2
- package/dist/react/EmailsTab-DbK4Eobn.js +139 -0
- package/dist/react/{EventsSection-ByQ-9blq.js → EventsSection-C7RQW_LY.js} +2 -2
- package/dist/react/EventsTab-CfVr7AiM.js +57 -0
- package/dist/react/{FilterBar-DQRXpWrb.js → FilterBar-CQ7bD669.js} +15 -15
- package/dist/react/{JobsSection-DF3qEv9O.js → JobsSection-CQHNK_Ls.js} +2 -2
- package/dist/react/{JobsTab-BbrBWIOb.js → JobsTab-znzf6jzk.js} +54 -42
- package/dist/react/{LogsSection-DcFTZY7b.js → LogsSection-Dmm3rE2B.js} +9 -3
- package/dist/react/LogsTab-D8unMV5P.js +108 -0
- package/dist/react/{OverviewSection-C4T1ur51.js → OverviewSection-ABP9ueBo.js} +1 -1
- package/dist/react/{QueriesSection-PswteoF9.js → QueriesSection-CnmSkznA.js} +2 -2
- package/dist/react/{QueriesTab-osLUWd4L.js → QueriesTab-BQzcxEiW.js} +37 -40
- package/dist/react/{RelatedLogs-DFDOyUMr.js → RelatedLogs-3A8RuGKH.js} +15 -3
- package/dist/react/{RequestsSection-Nag30rEA.js → RequestsSection-kW79_M7k.js} +3 -3
- package/dist/react/{RoutesSection-BUSkM6PY.js → RoutesSection-BRhxrtjZ.js} +2 -2
- package/dist/react/RoutesTab-CpYH5lUw.js +68 -0
- package/dist/react/{TimelineTab-Covg5weo.js → TimelineTab-DjLR35Ce.js} +47 -53
- package/dist/react/index-CsImORX6.js +1121 -0
- package/dist/react/index.js +1 -1
- package/dist/react/react/components/{Dashboard/shared → shared}/FilterBar.d.ts +4 -3
- package/dist/react/react/hooks/useDashboardData.d.ts +4 -8
- package/dist/react/style.css +1 -1
- package/dist/src/collectors/app_collector.d.ts +0 -8
- package/dist/src/collectors/app_collector.js +45 -52
- package/dist/src/collectors/auto_detect.d.ts +0 -23
- package/dist/src/collectors/auto_detect.js +33 -55
- package/dist/src/collectors/db_pool_collector.d.ts +14 -16
- package/dist/src/collectors/db_pool_collector.js +72 -57
- package/dist/src/collectors/log_collector.d.ts +0 -47
- package/dist/src/collectors/log_collector.js +36 -65
- package/dist/src/collectors/queue_collector.d.ts +0 -20
- package/dist/src/collectors/queue_collector.js +60 -76
- package/dist/src/collectors/redis_collector.d.ts +10 -10
- package/dist/src/collectors/redis_collector.js +69 -66
- package/dist/src/config/deprecation_migration.d.ts +7 -0
- package/dist/src/config/deprecation_migration.js +201 -0
- package/dist/src/controller/debug_controller.d.ts +1 -1
- package/dist/src/controller/debug_controller.js +87 -81
- package/dist/src/dashboard/cache_handlers.d.ts +14 -0
- package/dist/src/dashboard/cache_handlers.js +52 -0
- package/dist/src/dashboard/chart_aggregator.d.ts +0 -7
- package/dist/src/dashboard/chart_aggregator.js +68 -50
- package/dist/src/dashboard/coalesce_cache.d.ts +25 -0
- package/dist/src/dashboard/coalesce_cache.js +47 -0
- package/dist/src/dashboard/dashboard_controller.d.ts +11 -37
- package/dist/src/dashboard/dashboard_controller.js +51 -544
- package/dist/src/dashboard/dashboard_page_assets.d.ts +17 -0
- package/dist/src/dashboard/dashboard_page_assets.js +51 -0
- package/dist/src/dashboard/dashboard_store.d.ts +19 -218
- package/dist/src/dashboard/dashboard_store.js +115 -1116
- package/dist/src/dashboard/dashboard_types.d.ts +83 -0
- package/dist/src/dashboard/dashboard_types.js +4 -0
- package/dist/src/dashboard/detail_queries.d.ts +19 -0
- package/dist/src/dashboard/detail_queries.js +98 -0
- package/dist/src/dashboard/email_event_builder.d.ts +8 -0
- package/dist/src/dashboard/email_event_builder.js +65 -0
- package/dist/src/dashboard/explain_query.d.ts +8 -0
- package/dist/src/dashboard/explain_query.js +22 -0
- package/dist/src/dashboard/filter_handlers.d.ts +23 -0
- package/dist/src/dashboard/filter_handlers.js +56 -0
- package/dist/src/dashboard/filtered_queries.d.ts +15 -0
- package/dist/src/dashboard/filtered_queries.js +155 -0
- package/dist/src/dashboard/flush_manager.d.ts +25 -0
- package/dist/src/dashboard/flush_manager.js +107 -0
- package/dist/src/dashboard/format_helpers.d.ts +126 -0
- package/dist/src/dashboard/format_helpers.js +140 -0
- package/dist/src/dashboard/inspector_manager.d.ts +36 -0
- package/dist/src/dashboard/inspector_manager.js +102 -0
- package/dist/src/dashboard/integrations/config_inspector.js +11 -13
- package/dist/src/dashboard/integrations/queue_inspector.d.ts +3 -3
- package/dist/src/dashboard/integrations/queue_inspector.js +13 -10
- package/dist/src/dashboard/jobs_handlers.d.ts +14 -0
- package/dist/src/dashboard/jobs_handlers.js +61 -0
- package/dist/src/dashboard/knex_factory.d.ts +18 -0
- package/dist/src/dashboard/knex_factory.js +91 -0
- package/dist/src/dashboard/migrator.js +30 -159
- package/dist/src/dashboard/migrator_tables.d.ts +19 -0
- package/dist/src/dashboard/migrator_tables.js +153 -0
- package/dist/src/dashboard/overview_queries.d.ts +66 -0
- package/dist/src/dashboard/overview_queries.js +155 -0
- package/dist/src/dashboard/overview_query_runners.d.ts +25 -0
- package/dist/src/dashboard/overview_query_runners.js +84 -0
- package/dist/src/dashboard/overview_store_queries.d.ts +40 -0
- package/dist/src/dashboard/overview_store_queries.js +69 -0
- package/dist/src/dashboard/paginate_helper.d.ts +12 -0
- package/dist/src/dashboard/paginate_helper.js +33 -0
- package/dist/src/dashboard/query_explain_handler.d.ts +10 -0
- package/dist/src/dashboard/query_explain_handler.js +80 -0
- package/dist/src/dashboard/read_queries.d.ts +32 -0
- package/dist/src/dashboard/read_queries.js +107 -0
- package/dist/src/dashboard/saved_filter_queries.d.ts +10 -0
- package/dist/src/dashboard/saved_filter_queries.js +24 -0
- package/dist/src/dashboard/storage_stats.d.ts +41 -0
- package/dist/src/dashboard/storage_stats.js +81 -0
- package/dist/src/dashboard/write_queue.d.ts +106 -0
- package/dist/src/dashboard/write_queue.js +225 -0
- package/dist/src/data/data_access.d.ts +2 -39
- package/dist/src/data/data_access.js +17 -193
- package/dist/src/data/data_access_helpers.d.ts +130 -0
- package/dist/src/data/data_access_helpers.js +212 -0
- package/dist/src/debug/debug_store.js +37 -32
- package/dist/src/debug/email_collector.d.ts +1 -10
- package/dist/src/debug/email_collector.js +78 -81
- package/dist/src/debug/event_collector.d.ts +0 -9
- package/dist/src/debug/event_collector.js +79 -62
- package/dist/src/debug/query_collector.js +23 -19
- package/dist/src/debug/route_inspector.d.ts +1 -5
- package/dist/src/debug/route_inspector.js +50 -51
- package/dist/src/debug/trace_collector.d.ts +9 -1
- package/dist/src/debug/trace_collector.js +21 -15
- package/dist/src/debug/types.d.ts +1 -1
- package/dist/src/define_config.d.ts +0 -65
- package/dist/src/define_config.js +93 -333
- 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/stats-bar.js +1 -1
- package/dist/src/edge/client-vue/dashboard.js +5 -5
- package/dist/src/edge/client-vue/debug-panel-deferred.js +3 -3
- package/dist/src/edge/client-vue/stats-bar.js +3 -3
- package/dist/src/edge/plugin.d.ts +0 -16
- package/dist/src/edge/plugin.js +57 -64
- package/dist/src/engine/request_metrics.d.ts +1 -0
- package/dist/src/engine/request_metrics.js +32 -42
- package/dist/src/middleware/request_tracking_middleware.d.ts +2 -8
- package/dist/src/middleware/request_tracking_middleware.js +65 -93
- package/dist/src/provider/auth_middleware_detector.d.ts +16 -0
- package/dist/src/provider/auth_middleware_detector.js +97 -0
- package/dist/src/provider/boot_helpers.d.ts +20 -0
- package/dist/src/provider/boot_helpers.js +91 -0
- package/dist/src/provider/boot_initializer.d.ts +28 -0
- package/dist/src/provider/boot_initializer.js +35 -0
- package/dist/src/provider/dashboard_init.d.ts +30 -0
- package/dist/src/provider/dashboard_init.js +138 -0
- package/dist/src/provider/dashboard_setup.d.ts +25 -0
- package/dist/src/provider/dashboard_setup.js +78 -0
- package/dist/src/provider/diagnostics.d.ts +134 -0
- package/dist/src/provider/diagnostics.js +127 -0
- package/dist/src/provider/email_bridge.d.ts +43 -0
- package/dist/src/provider/email_bridge.js +80 -0
- package/dist/src/provider/email_helpers.d.ts +13 -0
- package/dist/src/provider/email_helpers.js +68 -0
- package/dist/src/provider/pino_hook.d.ts +17 -0
- package/dist/src/provider/pino_hook.js +35 -0
- package/dist/src/provider/provider_helpers_extra.d.ts +47 -0
- package/dist/src/provider/provider_helpers_extra.js +177 -0
- package/dist/src/provider/server_stats_provider.d.ts +39 -85
- package/dist/src/provider/server_stats_provider.js +132 -951
- package/dist/src/provider/shutdown_helpers.d.ts +43 -0
- package/dist/src/provider/shutdown_helpers.js +70 -0
- package/dist/src/provider/toolbar_setup.d.ts +57 -0
- package/dist/src/provider/toolbar_setup.js +141 -0
- package/dist/src/routes/dashboard_routes.d.ts +14 -0
- package/dist/src/routes/dashboard_routes.js +197 -0
- package/dist/src/routes/debug_routes.d.ts +14 -0
- package/dist/src/routes/debug_routes.js +101 -0
- package/dist/src/routes/register_routes.d.ts +0 -78
- package/dist/src/routes/register_routes.js +22 -352
- package/dist/src/routes/stats_routes.d.ts +5 -0
- package/dist/src/routes/stats_routes.js +14 -0
- package/dist/src/styles/components.css +96 -0
- package/dist/src/styles/dashboard.css +8 -90
- package/dist/src/styles/debug-panel.css +1 -31
- package/dist/src/types.d.ts +305 -14
- package/dist/vue/{CacheSection-oFAJL3mo.js → CacheSection-ITqvpfH5.js} +1 -1
- package/dist/vue/{ConfigSection-BhfJ4KqL.js → ConfigSection-DTn3GslE.js} +1 -1
- package/dist/vue/{EmailsSection-BcNyhyHs.js → EmailsSection-DtLJ4XoS.js} +1 -1
- package/dist/vue/{EventsSection-r60Q5Lmu.js → EventsSection-BOYYz0Ty.js} +1 -1
- package/dist/vue/{JobsSection-BHL-hkQw.js → JobsSection-BazTxcJL.js} +1 -1
- package/dist/vue/{LogsSection-DRMGzJmg.js → LogsSection-D55PjTKX.js} +9 -3
- package/dist/vue/{LogsTab-Bg3o0Mm6.js → LogsTab-47zEK7jL.js} +4 -1
- package/dist/vue/{OverviewSection-CXh6Ja1B.js → OverviewSection-1uBKo-Tu.js} +1 -1
- package/dist/vue/{QueriesSection-IodIsCJ-.js → QueriesSection-rpoZ4ogd.js} +1 -1
- package/dist/vue/{RequestsSection-BPuMdmMc.js → RequestsSection-x7LvT0MC.js} +1 -1
- package/dist/vue/{RoutesSection-NKo3Rbq3.js → RoutesSection-CCD0zZqQ.js} +1 -1
- package/dist/vue/composables/useDashboardData.d.ts +12 -23
- package/dist/vue/index-C8MxnS7Q.js +1232 -0
- package/dist/vue/index.js +1 -1
- package/dist/vue/style.css +1 -1
- package/package.json +1 -1
- package/dist/react/CacheTab-CA8LB1J5.js +0 -123
- package/dist/react/CustomPaneTab-Bxtv_8Rw.js +0 -104
- package/dist/react/EmailsTab-BDhEiomM.js +0 -153
- package/dist/react/EventsTab-CMfY98Rl.js +0 -63
- package/dist/react/LogsTab-CicucmVk.js +0 -103
- package/dist/react/RoutesTab-DgVzd2PZ.js +0 -74
- package/dist/react/index-Cflz9Ebj.js +0 -1069
- package/dist/vue/index-Dtgysd26.js +0 -1229
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Saved filter CRUD operations for the DashboardStore.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from DashboardStore to reduce file length.
|
|
5
|
+
*/
|
|
6
|
+
export function fetchSavedFilters(db, cache, section) {
|
|
7
|
+
return cache.coalesce('savedFilters:' + (section || ''), async () => {
|
|
8
|
+
const q = db('server_stats_saved_filters').orderBy('created_at', 'desc');
|
|
9
|
+
if (section)
|
|
10
|
+
q.where('section', section);
|
|
11
|
+
return q;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export async function insertSavedFilter(db, name, section, filterConfig) {
|
|
15
|
+
const [id] = await db('server_stats_saved_filters').insert({
|
|
16
|
+
name,
|
|
17
|
+
section,
|
|
18
|
+
filter_config: JSON.stringify(filterConfig),
|
|
19
|
+
});
|
|
20
|
+
return { id, name, section, filterConfig };
|
|
21
|
+
}
|
|
22
|
+
export async function removeSavedFilter(db, id) {
|
|
23
|
+
return (await db('server_stats_saved_filters').where('id', id).delete()) > 0;
|
|
24
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage statistics queries for the dashboard diagnostics endpoint.
|
|
3
|
+
*/
|
|
4
|
+
import { CoalesceCache } from './coalesce_cache.js';
|
|
5
|
+
import type { Knex } from 'knex';
|
|
6
|
+
/**
|
|
7
|
+
* Get the file sizes of the SQLite database and its WAL file.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getFileSizes(dbFilePath: string): Promise<[number, number]>;
|
|
10
|
+
/**
|
|
11
|
+
* Count rows in all dashboard tables within a single transaction.
|
|
12
|
+
*/
|
|
13
|
+
export declare function countAllTables(db: Knex): Promise<Array<{
|
|
14
|
+
name: string;
|
|
15
|
+
rowCount: number;
|
|
16
|
+
}>>;
|
|
17
|
+
export interface StorageStatsResult {
|
|
18
|
+
ready: boolean;
|
|
19
|
+
dbPath: string;
|
|
20
|
+
fileSizeMb: number;
|
|
21
|
+
walSizeMb: number;
|
|
22
|
+
retentionDays: number;
|
|
23
|
+
tables: Array<{
|
|
24
|
+
name: string;
|
|
25
|
+
rowCount: number;
|
|
26
|
+
}>;
|
|
27
|
+
lastCleanupAt: number | null;
|
|
28
|
+
}
|
|
29
|
+
export interface StorageStatsOpts {
|
|
30
|
+
db: Knex;
|
|
31
|
+
cache: CoalesceCache;
|
|
32
|
+
dbFilePath: string;
|
|
33
|
+
dbPath: string;
|
|
34
|
+
retentionDays: number;
|
|
35
|
+
lastCleanupAt: number | null;
|
|
36
|
+
onResult: (stats: StorageStatsResult) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build storage stats by querying file sizes and table row counts.
|
|
40
|
+
*/
|
|
41
|
+
export declare function fetchStorageStats(opts: StorageStatsOpts): Promise<StorageStatsResult>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage statistics queries for the dashboard diagnostics endpoint.
|
|
3
|
+
*/
|
|
4
|
+
import { stat as fsStat } from 'node:fs/promises';
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// File size helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
/**
|
|
9
|
+
* Get the file sizes of the SQLite database and its WAL file.
|
|
10
|
+
*/
|
|
11
|
+
export async function getFileSizes(dbFilePath) {
|
|
12
|
+
let fileSizeMb = 0;
|
|
13
|
+
let walSizeMb = 0;
|
|
14
|
+
try {
|
|
15
|
+
const s = await fsStat(dbFilePath);
|
|
16
|
+
fileSizeMb = Math.round((s.size / (1024 * 1024)) * 100) / 100;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// File may not exist yet
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const ws = await fsStat(dbFilePath + '-wal');
|
|
23
|
+
walSizeMb = Math.round((ws.size / (1024 * 1024)) * 100) / 100;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// WAL file may not exist
|
|
27
|
+
}
|
|
28
|
+
return [fileSizeMb, walSizeMb];
|
|
29
|
+
}
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Table row counts
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
const TABLE_NAMES = [
|
|
34
|
+
'server_stats_requests',
|
|
35
|
+
'server_stats_queries',
|
|
36
|
+
'server_stats_events',
|
|
37
|
+
'server_stats_emails',
|
|
38
|
+
'server_stats_logs',
|
|
39
|
+
'server_stats_traces',
|
|
40
|
+
'server_stats_metrics',
|
|
41
|
+
'server_stats_saved_filters',
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* Count rows in all dashboard tables within a single transaction.
|
|
45
|
+
*/
|
|
46
|
+
export async function countAllTables(db) {
|
|
47
|
+
return db.transaction(async (trx) => {
|
|
48
|
+
const result = [];
|
|
49
|
+
for (const name of TABLE_NAMES) {
|
|
50
|
+
try {
|
|
51
|
+
const [row] = await trx(name).count('* as count');
|
|
52
|
+
result.push({ name, rowCount: Number(row.count) });
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
result.push({ name, rowCount: 0 });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Build storage stats by querying file sizes and table row counts.
|
|
63
|
+
*/
|
|
64
|
+
export function fetchStorageStats(opts) {
|
|
65
|
+
const { db, cache, dbFilePath, dbPath, retentionDays, lastCleanupAt, onResult } = opts;
|
|
66
|
+
return cache.coalesce('storageStats', async () => {
|
|
67
|
+
const [fileSizeMb, walSizeMb] = await getFileSizes(dbFilePath);
|
|
68
|
+
const tables = await countAllTables(db);
|
|
69
|
+
const stats = {
|
|
70
|
+
ready: true,
|
|
71
|
+
dbPath,
|
|
72
|
+
fileSizeMb,
|
|
73
|
+
walSizeMb,
|
|
74
|
+
retentionDays,
|
|
75
|
+
tables,
|
|
76
|
+
lastCleanupAt,
|
|
77
|
+
};
|
|
78
|
+
onResult(stats);
|
|
79
|
+
return stats;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data preparation helpers for the DashboardStore write queue.
|
|
3
|
+
*
|
|
4
|
+
* These functions transform in-memory records into SQLite-ready row
|
|
5
|
+
* objects. They are pure (no I/O, no Knex dependency) so they can
|
|
6
|
+
* be tested in isolation.
|
|
7
|
+
*/
|
|
8
|
+
import type { EventRecord, EmailRecord } from '../debug/types.js';
|
|
9
|
+
import type { PersistRequestInput } from './dashboard_types.js';
|
|
10
|
+
import type { Knex } from 'knex';
|
|
11
|
+
export declare function hasWarned(path: string): boolean;
|
|
12
|
+
export declare function markWarned(path: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* Normalize a SQL query by replacing literal values with `?` placeholders.
|
|
15
|
+
* Used for grouping identical query patterns.
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizeSql(sql: string): string;
|
|
18
|
+
export interface PreparedQuery {
|
|
19
|
+
sql_text: string;
|
|
20
|
+
sql_normalized: string;
|
|
21
|
+
bindings: string | null;
|
|
22
|
+
duration: number;
|
|
23
|
+
method: string;
|
|
24
|
+
model: string | null;
|
|
25
|
+
connection: string;
|
|
26
|
+
in_transaction: number;
|
|
27
|
+
}
|
|
28
|
+
export interface PreparedTraceRow {
|
|
29
|
+
method: string;
|
|
30
|
+
url: string;
|
|
31
|
+
status_code: number;
|
|
32
|
+
total_duration: number;
|
|
33
|
+
span_count: number;
|
|
34
|
+
spans: string;
|
|
35
|
+
warnings: string | null;
|
|
36
|
+
}
|
|
37
|
+
export interface PreparedRequest {
|
|
38
|
+
input: PersistRequestInput;
|
|
39
|
+
filteredQueries: PreparedQuery[];
|
|
40
|
+
traceRow: PreparedTraceRow | null;
|
|
41
|
+
}
|
|
42
|
+
export interface PreparedLog {
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
level: string;
|
|
45
|
+
message: string;
|
|
46
|
+
request_id: string | null;
|
|
47
|
+
data: string;
|
|
48
|
+
}
|
|
49
|
+
export interface EmailRow {
|
|
50
|
+
[key: string]: unknown;
|
|
51
|
+
from_addr: string;
|
|
52
|
+
to_addr: string;
|
|
53
|
+
cc: string | null;
|
|
54
|
+
bcc: string | null;
|
|
55
|
+
subject: string;
|
|
56
|
+
html: string | null;
|
|
57
|
+
text_body: string | null;
|
|
58
|
+
mailer: string;
|
|
59
|
+
status: string;
|
|
60
|
+
message_id: string | null;
|
|
61
|
+
attachment_count: number;
|
|
62
|
+
}
|
|
63
|
+
export interface EventRow {
|
|
64
|
+
[key: string]: unknown;
|
|
65
|
+
request_id: null;
|
|
66
|
+
event_name: string;
|
|
67
|
+
data: string | null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Pre-stringify and transform request inputs into SQLite-ready row objects.
|
|
71
|
+
* This is done OUTSIDE the transaction so the synchronous better-sqlite3
|
|
72
|
+
* execution does not block the event loop on large spans.
|
|
73
|
+
*/
|
|
74
|
+
export declare function prepareRequestRows(requests: PersistRequestInput[]): PreparedRequest[];
|
|
75
|
+
/**
|
|
76
|
+
* Transform raw log entries into SQLite-ready row objects.
|
|
77
|
+
*/
|
|
78
|
+
export declare function prepareLogRows(logs: Record<string, unknown>[]): PreparedLog[];
|
|
79
|
+
/**
|
|
80
|
+
* Transform an EmailRecord into a SQLite-ready row object.
|
|
81
|
+
*/
|
|
82
|
+
export declare function buildEmailRow(record: EmailRecord): EmailRow;
|
|
83
|
+
/**
|
|
84
|
+
* Transform EventRecords into SQLite-ready row objects.
|
|
85
|
+
*/
|
|
86
|
+
export declare function buildEventRows(events: EventRecord[]): EventRow[];
|
|
87
|
+
/**
|
|
88
|
+
* Insert rows into a table in batches of 50.
|
|
89
|
+
*/
|
|
90
|
+
export declare function batchInsert(trx: Knex.Transaction, table: string, rows: Record<string, unknown>[]): Promise<void>;
|
|
91
|
+
export declare function flushRequests(trx: Knex.Transaction, preparedRequests: PreparedRequest[]): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Flush pending events into the database.
|
|
94
|
+
*/
|
|
95
|
+
export declare function flushEvents(trx: Knex.Transaction, events: {
|
|
96
|
+
requestIndex: number;
|
|
97
|
+
events: EventRecord[];
|
|
98
|
+
}[]): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Flush pending emails into the database.
|
|
101
|
+
*/
|
|
102
|
+
export declare function flushEmails(trx: Knex.Transaction, emails: EmailRecord[]): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Flush prepared logs into the database.
|
|
105
|
+
*/
|
|
106
|
+
export declare function flushLogs(trx: Knex.Transaction, preparedLogs: PreparedLog[]): Promise<void>;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data preparation helpers for the DashboardStore write queue.
|
|
3
|
+
*
|
|
4
|
+
* These functions transform in-memory records into SQLite-ready row
|
|
5
|
+
* objects. They are pure (no I/O, no Knex dependency) so they can
|
|
6
|
+
* be tested in isolation.
|
|
7
|
+
*/
|
|
8
|
+
import { round } from '../utils/math_helpers.js';
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Warn-once tracking for write-path catch blocks
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
const warnedWritePaths = new Set();
|
|
13
|
+
export function hasWarned(path) {
|
|
14
|
+
return warnedWritePaths.has(path);
|
|
15
|
+
}
|
|
16
|
+
export function markWarned(path) {
|
|
17
|
+
warnedWritePaths.add(path);
|
|
18
|
+
}
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// SQL normalization
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
/**
|
|
23
|
+
* Normalize a SQL query by replacing literal values with `?` placeholders.
|
|
24
|
+
* Used for grouping identical query patterns.
|
|
25
|
+
*/
|
|
26
|
+
export function normalizeSql(sql) {
|
|
27
|
+
return sql
|
|
28
|
+
.replace(/'[^']*'/g, '?')
|
|
29
|
+
.replace(/\b\d+(\.\d+)?\b/g, '?')
|
|
30
|
+
.replace(/\s+/g, ' ')
|
|
31
|
+
.trim();
|
|
32
|
+
}
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Pure data-prep functions
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/**
|
|
37
|
+
* Pre-stringify and transform request inputs into SQLite-ready row objects.
|
|
38
|
+
* This is done OUTSIDE the transaction so the synchronous better-sqlite3
|
|
39
|
+
* execution does not block the event loop on large spans.
|
|
40
|
+
*/
|
|
41
|
+
export function prepareRequestRows(requests) {
|
|
42
|
+
return requests.map((input) => ({
|
|
43
|
+
input,
|
|
44
|
+
filteredQueries: input.queries
|
|
45
|
+
.filter((q) => q.connection !== 'server_stats')
|
|
46
|
+
.map((q) => ({
|
|
47
|
+
sql_text: q.sql,
|
|
48
|
+
sql_normalized: normalizeSql(q.sql),
|
|
49
|
+
bindings: q.bindings ? JSON.stringify(q.bindings) : null,
|
|
50
|
+
duration: round(q.duration),
|
|
51
|
+
method: q.method,
|
|
52
|
+
model: q.model,
|
|
53
|
+
connection: q.connection,
|
|
54
|
+
in_transaction: q.inTransaction ? 1 : 0,
|
|
55
|
+
})),
|
|
56
|
+
traceRow: input.trace
|
|
57
|
+
? {
|
|
58
|
+
method: input.trace.method,
|
|
59
|
+
url: input.trace.url,
|
|
60
|
+
status_code: input.trace.statusCode,
|
|
61
|
+
total_duration: round(input.trace.totalDuration),
|
|
62
|
+
span_count: input.trace.spanCount,
|
|
63
|
+
spans: JSON.stringify(input.trace.spans),
|
|
64
|
+
warnings: input.trace.warnings.length > 0 ? JSON.stringify(input.trace.warnings) : null,
|
|
65
|
+
}
|
|
66
|
+
: null,
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Transform raw log entries into SQLite-ready row objects.
|
|
71
|
+
*/
|
|
72
|
+
export function prepareLogRows(logs) {
|
|
73
|
+
return logs.map((entry) => {
|
|
74
|
+
const levelName = typeof entry.levelName === 'string' ? entry.levelName : String(entry.level || 'unknown');
|
|
75
|
+
return {
|
|
76
|
+
level: levelName,
|
|
77
|
+
message: String(entry.msg || entry.message || ''),
|
|
78
|
+
request_id: entry.request_id || entry.requestId || entry['x-request-id']
|
|
79
|
+
? String(entry.request_id || entry.requestId || entry['x-request-id'])
|
|
80
|
+
: null,
|
|
81
|
+
data: JSON.stringify(entry),
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Transform an EmailRecord into a SQLite-ready row object.
|
|
87
|
+
*/
|
|
88
|
+
export function buildEmailRow(record) {
|
|
89
|
+
return {
|
|
90
|
+
from_addr: record.from,
|
|
91
|
+
to_addr: record.to,
|
|
92
|
+
cc: record.cc,
|
|
93
|
+
bcc: record.bcc,
|
|
94
|
+
subject: record.subject,
|
|
95
|
+
html: record.html,
|
|
96
|
+
text_body: record.text,
|
|
97
|
+
mailer: record.mailer,
|
|
98
|
+
status: record.status,
|
|
99
|
+
message_id: record.messageId,
|
|
100
|
+
attachment_count: record.attachmentCount,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Transform EventRecords into SQLite-ready row objects.
|
|
105
|
+
*/
|
|
106
|
+
export function buildEventRows(events) {
|
|
107
|
+
return events.map((e) => ({
|
|
108
|
+
request_id: null,
|
|
109
|
+
event_name: e.event,
|
|
110
|
+
data: e.data,
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Batch insert helper
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
/**
|
|
117
|
+
* Insert rows into a table in batches of 50.
|
|
118
|
+
*/
|
|
119
|
+
export async function batchInsert(trx, table, rows) {
|
|
120
|
+
for (let i = 0; i < rows.length; i += 50) {
|
|
121
|
+
await trx(table).insert(rows.slice(i, i + 50));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Transaction sub-routines for flushWriteQueue
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
/**
|
|
128
|
+
* Flush prepared requests (with queries + traces) into the database.
|
|
129
|
+
*/
|
|
130
|
+
/** Build the request row object for insertion. */
|
|
131
|
+
function buildRequestRow(input) {
|
|
132
|
+
const row = {
|
|
133
|
+
method: input.method,
|
|
134
|
+
url: input.url,
|
|
135
|
+
status_code: input.statusCode,
|
|
136
|
+
duration: round(input.duration),
|
|
137
|
+
span_count: input.trace?.spanCount ?? 0,
|
|
138
|
+
warning_count: input.trace?.warnings?.length ?? 0,
|
|
139
|
+
};
|
|
140
|
+
if (input.httpRequestId) {
|
|
141
|
+
row.http_request_id = String(input.httpRequestId);
|
|
142
|
+
}
|
|
143
|
+
return row;
|
|
144
|
+
}
|
|
145
|
+
/** Insert a single prepared request with its queries and trace. */
|
|
146
|
+
async function insertOneRequest(trx, prepared) {
|
|
147
|
+
const { input, filteredQueries, traceRow } = prepared;
|
|
148
|
+
const row = buildRequestRow(input);
|
|
149
|
+
const [requestId] = await trx('server_stats_requests').insert(row);
|
|
150
|
+
const hasId = requestId !== null && requestId !== undefined;
|
|
151
|
+
if (hasId && filteredQueries.length > 0) {
|
|
152
|
+
const rows = filteredQueries.map((q) => ({ ...q, request_id: requestId }));
|
|
153
|
+
await batchInsert(trx, 'server_stats_queries', rows);
|
|
154
|
+
}
|
|
155
|
+
if (hasId && traceRow) {
|
|
156
|
+
await trx('server_stats_traces').insert({ ...traceRow, request_id: requestId });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export async function flushRequests(trx, preparedRequests) {
|
|
160
|
+
for (const prepared of preparedRequests) {
|
|
161
|
+
try {
|
|
162
|
+
await insertOneRequest(trx, prepared);
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
if (!hasWarned('persistRequest')) {
|
|
166
|
+
markWarned('persistRequest');
|
|
167
|
+
const { log } = await import('../utils/logger.js');
|
|
168
|
+
log.warn(`dashboard: persistRequest failed — ${err?.message}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Flush pending events into the database.
|
|
175
|
+
*/
|
|
176
|
+
export async function flushEvents(trx, events) {
|
|
177
|
+
for (const { events: evts } of events) {
|
|
178
|
+
try {
|
|
179
|
+
const rows = buildEventRows(evts);
|
|
180
|
+
await batchInsert(trx, 'server_stats_events', rows);
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
if (!hasWarned('recordEvents')) {
|
|
184
|
+
markWarned('recordEvents');
|
|
185
|
+
const { log } = await import('../utils/logger.js');
|
|
186
|
+
log.warn(`dashboard: recordEvents failed — ${err?.message}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Flush pending emails into the database.
|
|
193
|
+
*/
|
|
194
|
+
export async function flushEmails(trx, emails) {
|
|
195
|
+
if (emails.length === 0)
|
|
196
|
+
return;
|
|
197
|
+
try {
|
|
198
|
+
const rows = emails.map((record) => buildEmailRow(record));
|
|
199
|
+
await batchInsert(trx, 'server_stats_emails', rows);
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
if (!hasWarned('recordEmail')) {
|
|
203
|
+
markWarned('recordEmail');
|
|
204
|
+
const { log } = await import('../utils/logger.js');
|
|
205
|
+
log.warn(`dashboard: recordEmail failed — ${err?.message}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Flush prepared logs into the database.
|
|
211
|
+
*/
|
|
212
|
+
export async function flushLogs(trx, preparedLogs) {
|
|
213
|
+
if (preparedLogs.length === 0)
|
|
214
|
+
return;
|
|
215
|
+
try {
|
|
216
|
+
await batchInsert(trx, 'server_stats_logs', preparedLogs);
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
if (!hasWarned('recordLog')) {
|
|
220
|
+
markWarned('recordLog');
|
|
221
|
+
const { log } = await import('../utils/logger.js');
|
|
222
|
+
log.warn(`dashboard: recordLog failed — ${err?.message}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -1,32 +1,5 @@
|
|
|
1
|
-
import type { DashboardStore } from '
|
|
2
|
-
|
|
3
|
-
import type { QueryRecord, EventRecord, TraceRecord, RouteRecord } from '../debug/types.js';
|
|
4
|
-
export interface ListOptions {
|
|
5
|
-
page?: number;
|
|
6
|
-
perPage?: number;
|
|
7
|
-
search?: string;
|
|
8
|
-
sort?: string;
|
|
9
|
-
sortDir?: 'asc' | 'desc';
|
|
10
|
-
filters?: Record<string, unknown>;
|
|
11
|
-
/**
|
|
12
|
-
* Force the data source for this read.
|
|
13
|
-
*
|
|
14
|
-
* - `'memory'` — always read from ring buffers ({@link DebugStore}).
|
|
15
|
-
* Use this for the debug panel, which expects camelCase field names
|
|
16
|
-
* matching the {@link QueryRecord}/{@link EventRecord}/etc. interfaces.
|
|
17
|
-
* - `'auto'` (default) — use SQLite when available, fall back to memory.
|
|
18
|
-
*/
|
|
19
|
-
source?: 'memory' | 'auto';
|
|
20
|
-
}
|
|
21
|
-
export interface PaginatedResult<T = Record<string, unknown>> {
|
|
22
|
-
data: T[];
|
|
23
|
-
meta: {
|
|
24
|
-
total: number;
|
|
25
|
-
page: number;
|
|
26
|
-
perPage: number;
|
|
27
|
-
lastPage: number;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
1
|
+
import type { ListOptions, PaginatedResult, DashboardStore, DebugStore, QueryRecord, EventRecord, TraceRecord, RouteRecord } from './data_access_helpers.js';
|
|
2
|
+
export type { ListOptions, PaginatedResult };
|
|
30
3
|
/**
|
|
31
4
|
* Thin abstraction layer that delegates reads to either the
|
|
32
5
|
* {@link DashboardStore} (SQLite persistence) or the {@link DebugStore}
|
|
@@ -81,9 +54,6 @@ export declare class DataAccess {
|
|
|
81
54
|
getTraceDetail(id: number, source?: 'memory' | 'auto'): Promise<TraceRecord | null>;
|
|
82
55
|
/**
|
|
83
56
|
* Find log entries matching a specific request ID.
|
|
84
|
-
*
|
|
85
|
-
* Checks SQLite first (if available), then falls back to scanning
|
|
86
|
-
* the log file for entries with a matching `request_id` field.
|
|
87
57
|
*/
|
|
88
58
|
private getRelatedLogsByRequestId;
|
|
89
59
|
/**
|
|
@@ -101,11 +71,4 @@ export declare class DataAccess {
|
|
|
101
71
|
* 256 KB of the application log file from the filesystem.
|
|
102
72
|
*/
|
|
103
73
|
getLogs(opts?: ListOptions): Promise<PaginatedResult>;
|
|
104
|
-
/**
|
|
105
|
-
* Read and parse the last 256 KB of the application log file.
|
|
106
|
-
*
|
|
107
|
-
* Returns an array of enriched log entry objects. If the log file
|
|
108
|
-
* does not exist or cannot be read, returns an empty array.
|
|
109
|
-
*/
|
|
110
|
-
private readLogFile;
|
|
111
74
|
}
|