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.
Files changed (210) hide show
  1. package/README.md +23 -14
  2. package/dist/core/config-utils.d.ts +8 -0
  3. package/dist/core/constants.d.ts +4 -0
  4. package/dist/core/dashboard-data-controller.d.ts +16 -0
  5. package/dist/core/dashboard-data-helpers.d.ts +12 -0
  6. package/dist/core/debug-data-controller.d.ts +4 -0
  7. package/dist/core/define-config-helpers.d.ts +25 -0
  8. package/dist/core/feature-detect-helpers.d.ts +36 -0
  9. package/dist/core/formatters-helpers.d.ts +23 -0
  10. package/dist/core/index.js +594 -509
  11. package/dist/core/log-utils-helpers.d.ts +13 -0
  12. package/dist/core/metrics.d.ts +3 -28
  13. package/dist/core/pagination.d.ts +0 -9
  14. package/dist/core/server-stats-controller.d.ts +6 -0
  15. package/dist/core/transmit-helpers.d.ts +7 -0
  16. package/dist/core/types-dashboard.d.ts +178 -0
  17. package/dist/core/types-diagnostics.d.ts +85 -0
  18. package/dist/core/types.d.ts +10 -442
  19. package/dist/react/{CacheSection-UCMptWyn.js → CacheSection-baMZotSn.js} +2 -2
  20. package/dist/react/CacheTab-2cw_rMzj.js +117 -0
  21. package/dist/react/{ConfigSection-DfFd-WRq.js → ConfigSection-DGgqjAal.js} +1 -1
  22. package/dist/react/{ConfigTab-Bdg8YMer.js → ConfigTab-H3OnYqmK.js} +1 -1
  23. package/dist/react/CustomPaneTab-B6r7ha0u.js +98 -0
  24. package/dist/react/{EmailsSection-CM7stSyh.js → EmailsSection-C-UZISG-.js} +2 -2
  25. package/dist/react/EmailsTab-DbK4Eobn.js +139 -0
  26. package/dist/react/{EventsSection-ByQ-9blq.js → EventsSection-C7RQW_LY.js} +2 -2
  27. package/dist/react/EventsTab-CfVr7AiM.js +57 -0
  28. package/dist/react/{FilterBar-DQRXpWrb.js → FilterBar-CQ7bD669.js} +15 -15
  29. package/dist/react/{JobsSection-DF3qEv9O.js → JobsSection-CQHNK_Ls.js} +2 -2
  30. package/dist/react/{JobsTab-BbrBWIOb.js → JobsTab-znzf6jzk.js} +54 -42
  31. package/dist/react/{LogsSection-DcFTZY7b.js → LogsSection-Dmm3rE2B.js} +9 -3
  32. package/dist/react/LogsTab-D8unMV5P.js +108 -0
  33. package/dist/react/{OverviewSection-C4T1ur51.js → OverviewSection-ABP9ueBo.js} +1 -1
  34. package/dist/react/{QueriesSection-PswteoF9.js → QueriesSection-CnmSkznA.js} +2 -2
  35. package/dist/react/{QueriesTab-osLUWd4L.js → QueriesTab-BQzcxEiW.js} +37 -40
  36. package/dist/react/{RelatedLogs-DFDOyUMr.js → RelatedLogs-3A8RuGKH.js} +15 -3
  37. package/dist/react/{RequestsSection-Nag30rEA.js → RequestsSection-kW79_M7k.js} +3 -3
  38. package/dist/react/{RoutesSection-BUSkM6PY.js → RoutesSection-BRhxrtjZ.js} +2 -2
  39. package/dist/react/RoutesTab-CpYH5lUw.js +68 -0
  40. package/dist/react/{TimelineTab-Covg5weo.js → TimelineTab-DjLR35Ce.js} +47 -53
  41. package/dist/react/index-CsImORX6.js +1121 -0
  42. package/dist/react/index.js +1 -1
  43. package/dist/react/react/components/{Dashboard/shared → shared}/FilterBar.d.ts +4 -3
  44. package/dist/react/react/hooks/useDashboardData.d.ts +4 -8
  45. package/dist/react/style.css +1 -1
  46. package/dist/src/collectors/app_collector.d.ts +0 -8
  47. package/dist/src/collectors/app_collector.js +45 -52
  48. package/dist/src/collectors/auto_detect.d.ts +0 -23
  49. package/dist/src/collectors/auto_detect.js +33 -55
  50. package/dist/src/collectors/db_pool_collector.d.ts +14 -16
  51. package/dist/src/collectors/db_pool_collector.js +72 -57
  52. package/dist/src/collectors/log_collector.d.ts +0 -47
  53. package/dist/src/collectors/log_collector.js +36 -65
  54. package/dist/src/collectors/queue_collector.d.ts +0 -20
  55. package/dist/src/collectors/queue_collector.js +60 -76
  56. package/dist/src/collectors/redis_collector.d.ts +10 -10
  57. package/dist/src/collectors/redis_collector.js +69 -66
  58. package/dist/src/config/deprecation_migration.d.ts +7 -0
  59. package/dist/src/config/deprecation_migration.js +201 -0
  60. package/dist/src/controller/debug_controller.d.ts +1 -1
  61. package/dist/src/controller/debug_controller.js +87 -81
  62. package/dist/src/dashboard/cache_handlers.d.ts +14 -0
  63. package/dist/src/dashboard/cache_handlers.js +52 -0
  64. package/dist/src/dashboard/chart_aggregator.d.ts +0 -7
  65. package/dist/src/dashboard/chart_aggregator.js +68 -50
  66. package/dist/src/dashboard/coalesce_cache.d.ts +25 -0
  67. package/dist/src/dashboard/coalesce_cache.js +47 -0
  68. package/dist/src/dashboard/dashboard_controller.d.ts +11 -37
  69. package/dist/src/dashboard/dashboard_controller.js +51 -544
  70. package/dist/src/dashboard/dashboard_page_assets.d.ts +17 -0
  71. package/dist/src/dashboard/dashboard_page_assets.js +51 -0
  72. package/dist/src/dashboard/dashboard_store.d.ts +19 -218
  73. package/dist/src/dashboard/dashboard_store.js +115 -1116
  74. package/dist/src/dashboard/dashboard_types.d.ts +83 -0
  75. package/dist/src/dashboard/dashboard_types.js +4 -0
  76. package/dist/src/dashboard/detail_queries.d.ts +19 -0
  77. package/dist/src/dashboard/detail_queries.js +98 -0
  78. package/dist/src/dashboard/email_event_builder.d.ts +8 -0
  79. package/dist/src/dashboard/email_event_builder.js +65 -0
  80. package/dist/src/dashboard/explain_query.d.ts +8 -0
  81. package/dist/src/dashboard/explain_query.js +22 -0
  82. package/dist/src/dashboard/filter_handlers.d.ts +23 -0
  83. package/dist/src/dashboard/filter_handlers.js +56 -0
  84. package/dist/src/dashboard/filtered_queries.d.ts +15 -0
  85. package/dist/src/dashboard/filtered_queries.js +155 -0
  86. package/dist/src/dashboard/flush_manager.d.ts +25 -0
  87. package/dist/src/dashboard/flush_manager.js +107 -0
  88. package/dist/src/dashboard/format_helpers.d.ts +126 -0
  89. package/dist/src/dashboard/format_helpers.js +140 -0
  90. package/dist/src/dashboard/inspector_manager.d.ts +36 -0
  91. package/dist/src/dashboard/inspector_manager.js +102 -0
  92. package/dist/src/dashboard/integrations/config_inspector.js +11 -13
  93. package/dist/src/dashboard/integrations/queue_inspector.d.ts +3 -3
  94. package/dist/src/dashboard/integrations/queue_inspector.js +13 -10
  95. package/dist/src/dashboard/jobs_handlers.d.ts +14 -0
  96. package/dist/src/dashboard/jobs_handlers.js +61 -0
  97. package/dist/src/dashboard/knex_factory.d.ts +18 -0
  98. package/dist/src/dashboard/knex_factory.js +91 -0
  99. package/dist/src/dashboard/migrator.js +30 -159
  100. package/dist/src/dashboard/migrator_tables.d.ts +19 -0
  101. package/dist/src/dashboard/migrator_tables.js +153 -0
  102. package/dist/src/dashboard/overview_queries.d.ts +66 -0
  103. package/dist/src/dashboard/overview_queries.js +155 -0
  104. package/dist/src/dashboard/overview_query_runners.d.ts +25 -0
  105. package/dist/src/dashboard/overview_query_runners.js +84 -0
  106. package/dist/src/dashboard/overview_store_queries.d.ts +40 -0
  107. package/dist/src/dashboard/overview_store_queries.js +69 -0
  108. package/dist/src/dashboard/paginate_helper.d.ts +12 -0
  109. package/dist/src/dashboard/paginate_helper.js +33 -0
  110. package/dist/src/dashboard/query_explain_handler.d.ts +10 -0
  111. package/dist/src/dashboard/query_explain_handler.js +80 -0
  112. package/dist/src/dashboard/read_queries.d.ts +32 -0
  113. package/dist/src/dashboard/read_queries.js +107 -0
  114. package/dist/src/dashboard/saved_filter_queries.d.ts +10 -0
  115. package/dist/src/dashboard/saved_filter_queries.js +24 -0
  116. package/dist/src/dashboard/storage_stats.d.ts +41 -0
  117. package/dist/src/dashboard/storage_stats.js +81 -0
  118. package/dist/src/dashboard/write_queue.d.ts +106 -0
  119. package/dist/src/dashboard/write_queue.js +225 -0
  120. package/dist/src/data/data_access.d.ts +2 -39
  121. package/dist/src/data/data_access.js +17 -193
  122. package/dist/src/data/data_access_helpers.d.ts +130 -0
  123. package/dist/src/data/data_access_helpers.js +212 -0
  124. package/dist/src/debug/debug_store.js +37 -32
  125. package/dist/src/debug/email_collector.d.ts +1 -10
  126. package/dist/src/debug/email_collector.js +78 -81
  127. package/dist/src/debug/event_collector.d.ts +0 -9
  128. package/dist/src/debug/event_collector.js +79 -62
  129. package/dist/src/debug/query_collector.js +23 -19
  130. package/dist/src/debug/route_inspector.d.ts +1 -5
  131. package/dist/src/debug/route_inspector.js +50 -51
  132. package/dist/src/debug/trace_collector.d.ts +9 -1
  133. package/dist/src/debug/trace_collector.js +21 -15
  134. package/dist/src/debug/types.d.ts +1 -1
  135. package/dist/src/define_config.d.ts +0 -65
  136. package/dist/src/define_config.js +93 -333
  137. package/dist/src/edge/client/dashboard.js +2 -2
  138. package/dist/src/edge/client/debug-panel-deferred.js +1 -1
  139. package/dist/src/edge/client/stats-bar.js +1 -1
  140. package/dist/src/edge/client-vue/dashboard.js +5 -5
  141. package/dist/src/edge/client-vue/debug-panel-deferred.js +3 -3
  142. package/dist/src/edge/client-vue/stats-bar.js +3 -3
  143. package/dist/src/edge/plugin.d.ts +0 -16
  144. package/dist/src/edge/plugin.js +57 -64
  145. package/dist/src/engine/request_metrics.d.ts +1 -0
  146. package/dist/src/engine/request_metrics.js +32 -42
  147. package/dist/src/middleware/request_tracking_middleware.d.ts +2 -8
  148. package/dist/src/middleware/request_tracking_middleware.js +65 -93
  149. package/dist/src/provider/auth_middleware_detector.d.ts +16 -0
  150. package/dist/src/provider/auth_middleware_detector.js +97 -0
  151. package/dist/src/provider/boot_helpers.d.ts +20 -0
  152. package/dist/src/provider/boot_helpers.js +91 -0
  153. package/dist/src/provider/boot_initializer.d.ts +28 -0
  154. package/dist/src/provider/boot_initializer.js +35 -0
  155. package/dist/src/provider/dashboard_init.d.ts +30 -0
  156. package/dist/src/provider/dashboard_init.js +138 -0
  157. package/dist/src/provider/dashboard_setup.d.ts +25 -0
  158. package/dist/src/provider/dashboard_setup.js +78 -0
  159. package/dist/src/provider/diagnostics.d.ts +134 -0
  160. package/dist/src/provider/diagnostics.js +127 -0
  161. package/dist/src/provider/email_bridge.d.ts +43 -0
  162. package/dist/src/provider/email_bridge.js +80 -0
  163. package/dist/src/provider/email_helpers.d.ts +13 -0
  164. package/dist/src/provider/email_helpers.js +68 -0
  165. package/dist/src/provider/pino_hook.d.ts +17 -0
  166. package/dist/src/provider/pino_hook.js +35 -0
  167. package/dist/src/provider/provider_helpers_extra.d.ts +47 -0
  168. package/dist/src/provider/provider_helpers_extra.js +177 -0
  169. package/dist/src/provider/server_stats_provider.d.ts +39 -85
  170. package/dist/src/provider/server_stats_provider.js +132 -951
  171. package/dist/src/provider/shutdown_helpers.d.ts +43 -0
  172. package/dist/src/provider/shutdown_helpers.js +70 -0
  173. package/dist/src/provider/toolbar_setup.d.ts +57 -0
  174. package/dist/src/provider/toolbar_setup.js +141 -0
  175. package/dist/src/routes/dashboard_routes.d.ts +14 -0
  176. package/dist/src/routes/dashboard_routes.js +197 -0
  177. package/dist/src/routes/debug_routes.d.ts +14 -0
  178. package/dist/src/routes/debug_routes.js +101 -0
  179. package/dist/src/routes/register_routes.d.ts +0 -78
  180. package/dist/src/routes/register_routes.js +22 -352
  181. package/dist/src/routes/stats_routes.d.ts +5 -0
  182. package/dist/src/routes/stats_routes.js +14 -0
  183. package/dist/src/styles/components.css +96 -0
  184. package/dist/src/styles/dashboard.css +8 -90
  185. package/dist/src/styles/debug-panel.css +1 -31
  186. package/dist/src/types.d.ts +305 -14
  187. package/dist/vue/{CacheSection-oFAJL3mo.js → CacheSection-ITqvpfH5.js} +1 -1
  188. package/dist/vue/{ConfigSection-BhfJ4KqL.js → ConfigSection-DTn3GslE.js} +1 -1
  189. package/dist/vue/{EmailsSection-BcNyhyHs.js → EmailsSection-DtLJ4XoS.js} +1 -1
  190. package/dist/vue/{EventsSection-r60Q5Lmu.js → EventsSection-BOYYz0Ty.js} +1 -1
  191. package/dist/vue/{JobsSection-BHL-hkQw.js → JobsSection-BazTxcJL.js} +1 -1
  192. package/dist/vue/{LogsSection-DRMGzJmg.js → LogsSection-D55PjTKX.js} +9 -3
  193. package/dist/vue/{LogsTab-Bg3o0Mm6.js → LogsTab-47zEK7jL.js} +4 -1
  194. package/dist/vue/{OverviewSection-CXh6Ja1B.js → OverviewSection-1uBKo-Tu.js} +1 -1
  195. package/dist/vue/{QueriesSection-IodIsCJ-.js → QueriesSection-rpoZ4ogd.js} +1 -1
  196. package/dist/vue/{RequestsSection-BPuMdmMc.js → RequestsSection-x7LvT0MC.js} +1 -1
  197. package/dist/vue/{RoutesSection-NKo3Rbq3.js → RoutesSection-CCD0zZqQ.js} +1 -1
  198. package/dist/vue/composables/useDashboardData.d.ts +12 -23
  199. package/dist/vue/index-C8MxnS7Q.js +1232 -0
  200. package/dist/vue/index.js +1 -1
  201. package/dist/vue/style.css +1 -1
  202. package/package.json +1 -1
  203. package/dist/react/CacheTab-CA8LB1J5.js +0 -123
  204. package/dist/react/CustomPaneTab-Bxtv_8Rw.js +0 -104
  205. package/dist/react/EmailsTab-BDhEiomM.js +0 -153
  206. package/dist/react/EventsTab-CMfY98Rl.js +0 -63
  207. package/dist/react/LogsTab-CicucmVk.js +0 -103
  208. package/dist/react/RoutesTab-DgVzd2PZ.js +0 -74
  209. package/dist/react/index-Cflz9Ebj.js +0 -1069
  210. package/dist/vue/index-Dtgysd26.js +0 -1229
@@ -1,14 +1,5 @@
1
1
  import { appImport } from '../utils/app_import.js';
2
2
  import { log, dim, green, bold } from '../utils/logger.js';
3
- /**
4
- * Probe whether a package is importable at runtime.
5
- *
6
- * Uses {@link appImport} which resolves from `process.cwd()` first,
7
- * handling the common case where adonisjs-server-stats is symlinked
8
- * (e.g. `file:../../adonisjs-server-stats` in package.json). Without
9
- * this, `import(pkg)` resolves from the symlink *target* directory,
10
- * which may have devDependency stubs instead of the real packages.
11
- */
12
3
  async function isInstalled(pkg) {
13
4
  try {
14
5
  await appImport(pkg);
@@ -18,29 +9,16 @@ async function isInstalled(pkg) {
18
9
  return false;
19
10
  }
20
11
  }
21
- /**
22
- * Auto-detect which metric collectors to enable based on installed packages.
23
- *
24
- * Always enables collectors with no external dependencies:
25
- * - `processCollector` -- Node.js process metrics
26
- * - `systemCollector` -- OS-level system metrics
27
- * - `httpCollector` -- HTTP request metrics
28
- * - `logCollector` -- log stream metrics
29
- *
30
- * Conditionally enables collectors when their peer dependency is importable:
31
- * - `dbPoolCollector` + `appCollector` -- when `@adonisjs/lucid` is installed
32
- * - `redisCollector` -- when `@adonisjs/redis` is installed
33
- * - `queueCollector` -- when `bullmq` is installed
34
- *
35
- * This function is meant to run at startup, so the async overhead of
36
- * probing packages is acceptable.
37
- *
38
- * @returns Resolved collectors and detection metadata for the boot summary.
39
- */
40
- export async function autoDetectCollectors() {
41
- const collectors = [];
42
- const entries = [];
43
- // ── Always-on collectors (no external deps) ──────────────────────
12
+ /** Push an optional collector entry with auto-generated reason. */
13
+ function pushOptionalEntry(entries, opts) {
14
+ entries.push({
15
+ name: opts.name,
16
+ description: opts.description,
17
+ enabled: opts.enabled,
18
+ reason: opts.enabled ? `found ${opts.pkg}` : `install ${opts.pkg} to enable`,
19
+ });
20
+ }
21
+ async function registerCoreCollectors(collectors, entries) {
44
22
  const { processCollector } = await import('./process_collector.js');
45
23
  const { systemCollector } = await import('./system_collector.js');
46
24
  const { httpCollector } = await import('./http_collector.js');
@@ -53,7 +31,8 @@ export async function autoDetectCollectors() {
53
31
  entries.push({ name: 'http', description: 'req/s, latency, errors', enabled: true });
54
32
  collectors.push(logCollector());
55
33
  entries.push({ name: 'log', description: 'error & warning counts', enabled: true });
56
- // ── Conditional: @adonisjs/lucid ─────────────────────────────────
34
+ }
35
+ async function registerOptionalCollectors(collectors, entries) {
57
36
  const hasLucid = await isInstalled('@adonisjs/lucid');
58
37
  if (hasLucid) {
59
38
  const { dbPoolCollector } = await import('./db_pool_collector.js');
@@ -61,60 +40,59 @@ export async function autoDetectCollectors() {
61
40
  collectors.push(dbPoolCollector());
62
41
  collectors.push(appCollector());
63
42
  }
64
- entries.push({
43
+ pushOptionalEntry(entries, {
65
44
  name: 'db-pool',
66
45
  description: 'connection pool stats',
67
46
  enabled: hasLucid,
68
- reason: hasLucid ? 'found @adonisjs/lucid' : 'install @adonisjs/lucid to enable',
47
+ pkg: '@adonisjs/lucid',
69
48
  });
70
- entries.push({
49
+ pushOptionalEntry(entries, {
71
50
  name: 'app',
72
51
  description: 'app-level DB metrics',
73
52
  enabled: hasLucid,
74
- reason: hasLucid ? 'found @adonisjs/lucid' : 'install @adonisjs/lucid to enable',
53
+ pkg: '@adonisjs/lucid',
75
54
  });
76
- // ── Conditional: @adonisjs/redis ─────────────────────────────────
77
55
  const hasRedis = await isInstalled('@adonisjs/redis');
78
56
  if (hasRedis) {
79
57
  const { redisCollector } = await import('./redis_collector.js');
80
58
  collectors.push(redisCollector());
81
59
  }
82
- entries.push({
60
+ pushOptionalEntry(entries, {
83
61
  name: 'redis',
84
62
  description: 'connections, commands, memory',
85
63
  enabled: hasRedis,
86
- reason: hasRedis ? 'found @adonisjs/redis' : 'install @adonisjs/redis to enable',
64
+ pkg: '@adonisjs/redis',
87
65
  });
88
- // ── Conditional: bullmq ──────────────────────────────────────────
89
66
  const hasBullMQ = await isInstalled('bullmq');
90
67
  if (hasBullMQ) {
91
68
  const { queueCollector } = await import('./queue_collector.js');
92
- collectors.push(queueCollector({
93
- queueName: 'default',
94
- connection: { host: '127.0.0.1', port: 6379 },
95
- }));
69
+ collectors.push(queueCollector({ queueName: 'default', connection: { host: '127.0.0.1', port: 6379 } }));
96
70
  }
97
- entries.push({
71
+ pushOptionalEntry(entries, {
98
72
  name: 'queue',
99
73
  description: 'jobs, wait time, throughput',
100
74
  enabled: hasBullMQ,
101
- reason: hasBullMQ ? 'found bullmq' : 'install bullmq to enable',
75
+ pkg: 'bullmq',
102
76
  });
103
- // ── Rich boot log ────────────────────────────────────────────────
104
- const total = entries.length;
105
- const active = entries.filter((e) => e.enabled).length;
77
+ }
78
+ function printBootLog(entries) {
106
79
  const maxNameLen = Math.max(...entries.map((e) => e.name.length));
107
80
  const lines = entries.map((entry) => {
108
81
  const paddedName = entry.name.padEnd(maxNameLen);
109
82
  if (entry.enabled) {
110
- const mark = green('✔');
111
83
  const detail = entry.reason ? dim('— ' + entry.reason) : dim('— ' + entry.description);
112
- return ` ${mark} ${bold(paddedName)} ${detail}`;
84
+ return ` ${green('✔')} ${bold(paddedName)} ${detail}`;
113
85
  }
114
- const mark = dim('✗');
115
86
  const detail = dim('— ' + (entry.reason ?? entry.description));
116
- return ` ${mark} ${dim(paddedName)} ${detail}`;
87
+ return ` ${dim('✗')} ${dim(paddedName)} ${detail}`;
117
88
  });
118
89
  log.block('collectors (auto-detected):', lines);
119
- return { collectors, total, active };
90
+ }
91
+ export async function autoDetectCollectors() {
92
+ const collectors = [];
93
+ const entries = [];
94
+ await registerCoreCollectors(collectors, entries);
95
+ await registerOptionalCollectors(collectors, entries);
96
+ printBootLog(entries);
97
+ return { collectors, total: entries.length, active: entries.filter((e) => e.enabled).length };
120
98
  }
@@ -1,4 +1,18 @@
1
1
  import type { MetricCollector } from './collector.js';
2
+ /** Minimal interface for a Knex connection pool. */
3
+ export interface KnexPool {
4
+ numUsed?(): number;
5
+ numFree?(): number;
6
+ numPendingAcquires?(): number;
7
+ max?: number;
8
+ }
9
+ /** Extract pool stats from a Knex pool object, defaulting to 0. */
10
+ export declare function extractPoolMetrics(pool: Partial<KnexPool>): {
11
+ dbPoolUsed: number;
12
+ dbPoolFree: number;
13
+ dbPoolPending: number;
14
+ dbPoolMax: number;
15
+ };
2
16
  /**
3
17
  * Options for {@link dbPoolCollector}.
4
18
  */
@@ -15,22 +29,6 @@ export interface DbPoolCollectorOptions {
15
29
  /**
16
30
  * Monitors the Knex connection pool for a Lucid database connection.
17
31
  *
18
- * **Metrics produced:**
19
- * - `dbPoolUsed` -- connections currently checked out
20
- * - `dbPoolFree` -- idle connections available
21
- * - `dbPoolPending` -- queries waiting for a connection
22
- * - `dbPoolMax` -- maximum pool size
23
- *
24
- * Returns zeros if the connection or pool is unavailable.
25
- *
26
32
  * **Peer dependencies:** `@adonisjs/lucid`
27
- *
28
- * @example
29
- * ```ts
30
- * import { dbPoolCollector } from 'adonisjs-server-stats/collectors'
31
- *
32
- * dbPoolCollector() // monitor 'postgres'
33
- * dbPoolCollector({ connectionName: 'mysql' }) // monitor 'mysql'
34
- * ```
35
33
  */
36
34
  export declare function dbPoolCollector(opts?: DbPoolCollectorOptions): MetricCollector;
@@ -1,25 +1,20 @@
1
1
  import { appImport } from '../utils/app_import.js';
2
2
  import { log, dim, bold } from '../utils/logger.js';
3
+ /** Default pool metrics returned when the pool is unavailable. */
4
+ const POOL_DEFAULTS = { dbPoolUsed: 0, dbPoolFree: 0, dbPoolPending: 0, dbPoolMax: 0 };
5
+ /** Extract pool stats from a Knex pool object, defaulting to 0. */
6
+ export function extractPoolMetrics(pool) {
7
+ return {
8
+ dbPoolUsed: pool.numUsed?.() ?? 0,
9
+ dbPoolFree: pool.numFree?.() ?? 0,
10
+ dbPoolPending: pool.numPendingAcquires?.() ?? 0,
11
+ dbPoolMax: pool.max ?? 0,
12
+ };
13
+ }
3
14
  /**
4
15
  * Monitors the Knex connection pool for a Lucid database connection.
5
16
  *
6
- * **Metrics produced:**
7
- * - `dbPoolUsed` -- connections currently checked out
8
- * - `dbPoolFree` -- idle connections available
9
- * - `dbPoolPending` -- queries waiting for a connection
10
- * - `dbPoolMax` -- maximum pool size
11
- *
12
- * Returns zeros if the connection or pool is unavailable.
13
- *
14
17
  * **Peer dependencies:** `@adonisjs/lucid`
15
- *
16
- * @example
17
- * ```ts
18
- * import { dbPoolCollector } from 'adonisjs-server-stats/collectors'
19
- *
20
- * dbPoolCollector() // monitor 'postgres'
21
- * dbPoolCollector({ connectionName: 'mysql' }) // monitor 'mysql'
22
- * ```
23
18
  */
24
19
  export function dbPoolCollector(opts) {
25
20
  const connectionName = opts?.connectionName ?? 'postgres';
@@ -34,50 +29,70 @@ export function dbPoolCollector(opts) {
34
29
  },
35
30
  async collect() {
36
31
  try {
37
- const { default: db } = await appImport('@adonisjs/lucid/services/db');
38
- const connection = db.manager.get(connectionName);
39
- if (!connection) {
40
- if (!warnedConnectionNotFound) {
41
- warnedConnectionNotFound = true;
42
- log.block(`db_pool: connection ${bold(connectionName)} not found in db.manager`, [
43
- dim('The name must match a key in your') +
44
- ` ${bold('config/database.ts')} ` +
45
- dim('connections object.'),
46
- dim('Available connections are registered at app boot — double-check spelling.'),
47
- ]);
48
- }
49
- return { dbPoolUsed: 0, dbPoolFree: 0, dbPoolPending: 0, dbPoolMax: 0 };
50
- }
51
- const pool = connection.connection?.pool;
52
- if (!pool) {
53
- if (!warnedPoolUnavailable) {
54
- warnedPoolUnavailable = true;
55
- log.block(`db_pool: pool not available on connection ${bold(connectionName)}`, [
56
- dim('This usually means the connection has not been established yet,'),
57
- dim('or the database driver does not expose a Knex-style pool.'),
58
- dim('Pool metrics will report zeros until the pool is available.'),
59
- ]);
60
- }
61
- return { dbPoolUsed: 0, dbPoolFree: 0, dbPoolPending: 0, dbPoolMax: 0 };
62
- }
63
- return {
64
- dbPoolUsed: pool.numUsed?.() ?? 0,
65
- dbPoolFree: pool.numFree?.() ?? 0,
66
- dbPoolPending: pool.numPendingAcquires?.() ?? 0,
67
- dbPoolMax: pool.max ?? 0,
68
- };
32
+ return await collectPoolMetrics(connectionName, {
33
+ warnConnectionNotFound(alreadyWarned) {
34
+ if (!alreadyWarned)
35
+ warnedConnectionNotFound = true;
36
+ return warnedConnectionNotFound;
37
+ },
38
+ warnPoolUnavailable(alreadyWarned) {
39
+ if (!alreadyWarned)
40
+ warnedPoolUnavailable = true;
41
+ return warnedPoolUnavailable;
42
+ },
43
+ isConnectionNotFoundWarned: () => warnedConnectionNotFound,
44
+ isPoolUnavailableWarned: () => warnedPoolUnavailable,
45
+ });
69
46
  }
70
47
  catch {
71
- if (!warnedLucidMissing) {
72
- warnedLucidMissing = true;
73
- log.block(`db_pool: ${bold('@adonisjs/lucid')} is not installed or failed to import`, [
74
- dim('Install it with:'),
75
- ` ${bold('node ace add @adonisjs/lucid')}`,
76
- dim('Pool metrics will report zeros until the package is available.'),
77
- ]);
78
- }
79
- return { dbPoolUsed: 0, dbPoolFree: 0, dbPoolPending: 0, dbPoolMax: 0 };
48
+ warnLucidMissing(warnedLucidMissing);
49
+ warnedLucidMissing = true;
50
+ return POOL_DEFAULTS;
80
51
  }
81
52
  },
82
53
  };
83
54
  }
55
+ async function collectPoolMetrics(connectionName, warns) {
56
+ const { default: db } = await appImport('@adonisjs/lucid/services/db');
57
+ const connection = db.manager.get(connectionName);
58
+ if (!connection) {
59
+ warnConnectionNotFound(connectionName, warns.isConnectionNotFoundWarned());
60
+ warns.warnConnectionNotFound(true);
61
+ return POOL_DEFAULTS;
62
+ }
63
+ const pool = connection.connection?.pool;
64
+ if (!pool) {
65
+ warnPoolUnavailable(connectionName, warns.isPoolUnavailableWarned());
66
+ warns.warnPoolUnavailable(true);
67
+ return POOL_DEFAULTS;
68
+ }
69
+ return extractPoolMetrics(pool);
70
+ }
71
+ function warnConnectionNotFound(name, alreadyWarned) {
72
+ if (alreadyWarned)
73
+ return;
74
+ log.block(`db_pool: connection ${bold(name)} not found in db.manager`, [
75
+ dim('The name must match a key in your') +
76
+ ` ${bold('config/database.ts')} ` +
77
+ dim('connections object.'),
78
+ dim('Available connections are registered at app boot — double-check spelling.'),
79
+ ]);
80
+ }
81
+ function warnPoolUnavailable(name, alreadyWarned) {
82
+ if (alreadyWarned)
83
+ return;
84
+ log.block(`db_pool: pool not available on connection ${bold(name)}`, [
85
+ dim('This usually means the connection has not been established yet,'),
86
+ dim('or the database driver does not expose a Knex-style pool.'),
87
+ dim('Pool metrics will report zeros until the pool is available.'),
88
+ ]);
89
+ }
90
+ function warnLucidMissing(alreadyWarned) {
91
+ if (alreadyWarned)
92
+ return;
93
+ log.block(`db_pool: ${bold('@adonisjs/lucid')} is not installed or failed to import`, [
94
+ dim('Install it with:'),
95
+ ` ${bold('node ace add @adonisjs/lucid')}`,
96
+ dim('Pool metrics will report zeros until the package is available.'),
97
+ ]);
98
+ }
@@ -1,54 +1,7 @@
1
1
  import { LogStreamService } from '../log_stream/log_stream_service.js';
2
2
  import type { MetricCollector } from './collector.js';
3
- /**
4
- * Options for {@link logCollector}.
5
- */
6
3
  export interface LogCollectorOptions {
7
- /**
8
- * Path to the JSON log file to monitor.
9
- *
10
- * Relative to the application root. The file must contain
11
- * newline-delimited JSON entries (one per line) with `level`
12
- * and `time` fields (Pino format).
13
- *
14
- * @example `'logs/adonisjs.log'`
15
- */
16
4
  logPath: string;
17
5
  }
18
- /**
19
- * Returns the shared {@link LogStreamService} instance created by
20
- * `logCollector()`, or `null` if the log collector is not configured.
21
- *
22
- * Useful for accessing the log stream outside of the collector
23
- * (e.g. in the LogStreamProvider for live broadcasting).
24
- */
25
6
  export declare function getLogStreamService(): LogStreamService | null;
26
- /**
27
- * Monitors log entries and reports rolling error/warning counts.
28
- *
29
- * **Two modes:**
30
- * - **Zero-config (no args):** the provider auto-hooks into the AdonisJS
31
- * Pino logger at boot. Entries arrive in real-time, no file path needed.
32
- * - **File-based (with `logPath`):** polls a JSON log file every 2 seconds.
33
- * Use this as a fallback if the auto-hook doesn't work for your setup.
34
- *
35
- * **Metrics produced:**
36
- * - `logErrorsLast5m` -- error + fatal entries in the last 5 minutes
37
- * - `logWarningsLast5m` -- warning entries in the last 5 minutes
38
- * - `logEntriesLast5m` -- total entries in the last 5 minutes
39
- * - `logEntriesPerMinute` -- entries per minute (averaged over 5 min)
40
- *
41
- * **Peer dependencies:** none
42
- *
43
- * @example
44
- * ```ts
45
- * import { logCollector } from 'adonisjs-server-stats/collectors'
46
- *
47
- * // Zero-config — auto-hooks into AdonisJS logger (recommended)
48
- * logCollector()
49
- *
50
- * // File-based fallback
51
- * logCollector({ logPath: 'logs/adonisjs.log' })
52
- * ```
53
- */
54
7
  export declare function logCollector(opts?: LogCollectorOptions): MetricCollector;
@@ -2,44 +2,33 @@ import { existsSync } from 'node:fs';
2
2
  import { LogStreamService } from '../log_stream/log_stream_service.js';
3
3
  import { log, dim, bold } from '../utils/logger.js';
4
4
  let sharedLogStream = null;
5
- /**
6
- * Returns the shared {@link LogStreamService} instance created by
7
- * `logCollector()`, or `null` if the log collector is not configured.
8
- *
9
- * Useful for accessing the log stream outside of the collector
10
- * (e.g. in the LogStreamProvider for live broadcasting).
11
- */
12
5
  export function getLogStreamService() {
13
6
  return sharedLogStream;
14
7
  }
15
- /**
16
- * Monitors log entries and reports rolling error/warning counts.
17
- *
18
- * **Two modes:**
19
- * - **Zero-config (no args):** the provider auto-hooks into the AdonisJS
20
- * Pino logger at boot. Entries arrive in real-time, no file path needed.
21
- * - **File-based (with `logPath`):** polls a JSON log file every 2 seconds.
22
- * Use this as a fallback if the auto-hook doesn't work for your setup.
23
- *
24
- * **Metrics produced:**
25
- * - `logErrorsLast5m` -- error + fatal entries in the last 5 minutes
26
- * - `logWarningsLast5m` -- warning entries in the last 5 minutes
27
- * - `logEntriesLast5m` -- total entries in the last 5 minutes
28
- * - `logEntriesPerMinute` -- entries per minute (averaged over 5 min)
29
- *
30
- * **Peer dependencies:** none
31
- *
32
- * @example
33
- * ```ts
34
- * import { logCollector } from 'adonisjs-server-stats/collectors'
35
- *
36
- * // Zero-config — auto-hooks into AdonisJS logger (recommended)
37
- * logCollector()
38
- *
39
- * // File-based fallback
40
- * logCollector({ logPath: 'logs/adonisjs.log' })
41
- * ```
42
- */
8
+ function warnMissingFile(logPath, alreadyWarned) {
9
+ if (alreadyWarned)
10
+ return true;
11
+ log.warn(`Log file not found: ${bold(logPath)}`);
12
+ log.block('The log collector will keep retrying, but no metrics will appear until the file exists.', [
13
+ dim('Make sure the path is correct and your app is writing logs there.'),
14
+ dim('The file must contain newline-delimited JSON with') +
15
+ ` ${bold('level')} ` +
16
+ dim('and') +
17
+ ` ${bold('time')} ` +
18
+ dim('fields (Pino format).'),
19
+ ]);
20
+ return true;
21
+ }
22
+ function warnStartFailure(error, logPath, alreadyWarned) {
23
+ if (alreadyWarned)
24
+ return true;
25
+ log.warn(`Log collector failed to start: ${bold(String(error))}`);
26
+ log.block('The log collector will not produce metrics until this is resolved.', [
27
+ dim('Configured log path:') + ` ${bold(logPath)}`,
28
+ dim('Check file permissions and ensure the directory exists.'),
29
+ ]);
30
+ return true;
31
+ }
43
32
  export function logCollector(opts) {
44
33
  if (sharedLogStream) {
45
34
  console.warn('[server-stats] logCollector() called again — stopping previous LogStreamService instance');
@@ -47,8 +36,8 @@ export function logCollector(opts) {
47
36
  }
48
37
  const service = new LogStreamService(opts?.logPath);
49
38
  sharedLogStream = service;
50
- let warnedMissingFile = false;
51
- let warnedStartFailure = false;
39
+ let warnedMissing = false;
40
+ let warnedStart = false;
52
41
  return {
53
42
  name: 'log',
54
43
  label: opts?.logPath ? `log — file: ${opts.logPath}` : 'log — pino stream (zero-config)',
@@ -59,35 +48,17 @@ export function logCollector(opts) {
59
48
  };
60
49
  },
61
50
  async start() {
62
- if (opts?.logPath) {
63
- // File-based mode
64
- if (!existsSync(opts.logPath) && !warnedMissingFile) {
65
- warnedMissingFile = true;
66
- log.warn(`Log file not found: ${bold(opts.logPath)}`);
67
- log.block('The log collector will keep retrying, but no metrics will appear until the file exists.', [
68
- dim('Make sure the path is correct and your app is writing logs there.'),
69
- dim('The file must contain newline-delimited JSON with') +
70
- ` ${bold('level')} ` +
71
- dim('and') +
72
- ` ${bold('time')} ` +
73
- dim('fields (Pino format).'),
74
- ]);
75
- }
76
- try {
77
- await service.start();
78
- }
79
- catch (error) {
80
- if (!warnedStartFailure) {
81
- warnedStartFailure = true;
82
- log.warn(`Log collector failed to start: ${bold(String(error))}`);
83
- log.block('The log collector will not produce metrics until this is resolved.', [
84
- dim('Configured log path:') + ` ${bold(opts.logPath)}`,
85
- dim('Check file permissions and ensure the directory exists.'),
86
- ]);
87
- }
88
- }
51
+ if (!opts?.logPath)
52
+ return;
53
+ if (!existsSync(opts.logPath)) {
54
+ warnedMissing = warnMissingFile(opts.logPath, warnedMissing);
55
+ }
56
+ try {
57
+ await service.start();
58
+ }
59
+ catch (error) {
60
+ warnedStart = warnStartFailure(error, opts.logPath, warnedStart);
89
61
  }
90
- // Stream mode: no startup needed — entries arrive via ingest()
91
62
  },
92
63
  stop() {
93
64
  service.stop();
@@ -39,28 +39,8 @@ export interface QueueCollectorOptions {
39
39
  /**
40
40
  * Monitors a BullMQ job queue for active, waiting, delayed, and failed jobs.
41
41
  *
42
- * Creates a temporary BullMQ `Queue` instance each tick to fetch counts,
43
- * then immediately closes it.
44
- *
45
- * **Metrics produced:**
46
- * - `queueActive` -- jobs being processed
47
- * - `queueWaiting` -- jobs waiting for a worker
48
- * - `queueDelayed` -- jobs scheduled for the future
49
- * - `queueFailed` -- permanently failed jobs
50
- * - `queueWorkerCount` -- connected worker processes
51
- *
52
42
  * Returns zeros if BullMQ is unavailable or the queue cannot be reached.
53
43
  *
54
44
  * **Peer dependencies:** `bullmq`
55
- *
56
- * @example
57
- * ```ts
58
- * import { queueCollector } from 'adonisjs-server-stats/collectors'
59
- *
60
- * queueCollector({
61
- * queueName: 'default',
62
- * connection: { host: 'localhost', port: 6379, password: 'secret' },
63
- * })
64
- * ```
65
45
  */
66
46
  export declare function queueCollector(opts: QueueCollectorOptions): MetricCollector;