@oas-tools/oas-telemetry 0.7.1 → 0.8.0-alpha.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.
Files changed (167) hide show
  1. package/.env.example +21 -2
  2. package/README.md +1 -2
  3. package/dist/cjs/config/bootConfig.cjs +19 -14
  4. package/dist/cjs/config/config.cjs +112 -125
  5. package/dist/cjs/config/config.types.cjs +1 -4
  6. package/dist/cjs/docs/openapi.yaml +158 -4
  7. package/dist/cjs/index.cjs +27 -30
  8. package/dist/cjs/routesManager.cjs +62 -70
  9. package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.cjs +202 -190
  10. package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.cjs +204 -99
  11. package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.cjs +152 -116
  12. package/dist/cjs/telemetry/custom-implementations/instrumentations/logsInstrumentation.cjs +92 -0
  13. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/Chunk.cjs +159 -0
  14. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/Series.cjs +168 -0
  15. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/SeriesRegistry.cjs +389 -0
  16. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/types.cjs +2 -0
  17. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/utils.cjs +77 -0
  18. package/dist/cjs/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.cjs +65 -63
  19. package/dist/cjs/telemetry/custom-implementations/processors/dynamicMultiSpanProcessor.cjs +63 -62
  20. package/dist/cjs/telemetry/custom-implementations/utils/circular.cjs +47 -47
  21. package/dist/cjs/telemetry/custom-implementations/utils/storagePath.cjs +39 -0
  22. package/dist/cjs/telemetry/custom-implementations/wrappers.cjs +141 -138
  23. package/dist/cjs/telemetry/initializeTelemetry.cjs +35 -91
  24. package/dist/cjs/telemetry/telemetryConfigurator.cjs +70 -72
  25. package/dist/cjs/telemetry/telemetryRegistry.cjs +45 -31
  26. package/dist/cjs/tlm-ai/agent.cjs +49 -64
  27. package/dist/cjs/tlm-ai/aiController.cjs +54 -76
  28. package/dist/cjs/tlm-ai/aiRoutes.cjs +17 -20
  29. package/dist/cjs/tlm-ai/aiService.cjs +91 -95
  30. package/dist/cjs/tlm-ai/tools.cjs +177 -174
  31. package/dist/cjs/tlm-auth/authController.cjs +80 -123
  32. package/dist/cjs/tlm-auth/authMiddleware.cjs +25 -30
  33. package/dist/cjs/tlm-auth/authRoutes.cjs +11 -14
  34. package/dist/cjs/tlm-log/logController.cjs +171 -116
  35. package/dist/cjs/tlm-log/logRoutes.cjs +20 -20
  36. package/dist/cjs/tlm-metric/metricsController.cjs +211 -121
  37. package/dist/cjs/tlm-metric/metricsRoutes.cjs +23 -20
  38. package/dist/cjs/tlm-plugin/pluginController.cjs +128 -140
  39. package/dist/cjs/tlm-plugin/pluginProcess.cjs +89 -94
  40. package/dist/cjs/tlm-plugin/pluginRoutes.cjs +11 -14
  41. package/dist/cjs/tlm-plugin/pluginService.cjs +73 -74
  42. package/dist/cjs/tlm-trace/traceController.cjs +169 -117
  43. package/dist/cjs/tlm-trace/traceRoutes.cjs +20 -20
  44. package/dist/cjs/tlm-ui/uiRoutes.cjs +63 -32
  45. package/dist/cjs/tlm-util/utilController.cjs +68 -70
  46. package/dist/cjs/tlm-util/utilRoutes.cjs +51 -63
  47. package/dist/cjs/types/index.cjs +2 -5
  48. package/dist/cjs/utils/logger.cjs +38 -43
  49. package/dist/cjs/utils/regexUtils.cjs +22 -22
  50. package/dist/esm/config/bootConfig.js +6 -0
  51. package/dist/esm/config/config.js +1 -2
  52. package/dist/esm/docs/openapi.yaml +158 -4
  53. package/dist/esm/index.js +9 -8
  54. package/dist/esm/routesManager.js +6 -10
  55. package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.js +47 -8
  56. package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.js +164 -48
  57. package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.js +69 -29
  58. package/dist/esm/telemetry/custom-implementations/instrumentations/logsInstrumentation.js +85 -0
  59. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/Chunk.js +155 -0
  60. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/Series.js +164 -0
  61. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/SeriesRegistry.js +382 -0
  62. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/types.js +1 -0
  63. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/utils.js +74 -0
  64. package/dist/esm/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.js +2 -1
  65. package/dist/esm/telemetry/custom-implementations/processors/dynamicMultiSpanProcessor.js +1 -1
  66. package/dist/esm/telemetry/custom-implementations/utils/storagePath.js +33 -0
  67. package/dist/esm/telemetry/custom-implementations/wrappers.js +5 -2
  68. package/dist/esm/telemetry/initializeTelemetry.js +27 -69
  69. package/dist/esm/telemetry/telemetryConfigurator.js +42 -40
  70. package/dist/esm/telemetry/telemetryRegistry.js +12 -1
  71. package/dist/esm/tlm-ai/agent.js +5 -3
  72. package/dist/esm/tlm-ai/aiController.js +3 -3
  73. package/dist/esm/tlm-ai/aiService.js +6 -2
  74. package/dist/esm/tlm-ai/tools.js +5 -9
  75. package/dist/esm/tlm-auth/authController.js +3 -2
  76. package/dist/esm/tlm-log/logController.js +84 -4
  77. package/dist/esm/tlm-log/logRoutes.js +5 -2
  78. package/dist/esm/tlm-metric/metricsController.js +172 -49
  79. package/dist/esm/tlm-metric/metricsRoutes.js +10 -4
  80. package/dist/esm/tlm-plugin/pluginController.js +6 -11
  81. package/dist/esm/tlm-plugin/pluginService.js +2 -4
  82. package/dist/esm/tlm-trace/traceController.js +102 -16
  83. package/dist/esm/tlm-trace/traceRoutes.js +5 -2
  84. package/dist/esm/tlm-ui/uiRoutes.js +5 -5
  85. package/dist/esm/tlm-util/utilController.js +3 -9
  86. package/dist/esm/tlm-util/utilRoutes.js +2 -2
  87. package/dist/types/config/bootConfig.d.ts +4 -0
  88. package/dist/types/config/config.d.ts +36 -7
  89. package/dist/types/config/config.types.d.ts +6 -0
  90. package/dist/types/index.d.ts +2 -3
  91. package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.d.ts +4 -1
  92. package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.d.ts +60 -15
  93. package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.d.ts +9 -4
  94. package/dist/types/telemetry/custom-implementations/instrumentations/logsInstrumentation.d.ts +23 -0
  95. package/dist/types/telemetry/custom-implementations/metrics/tsdb/Chunk.d.ts +49 -0
  96. package/dist/types/telemetry/custom-implementations/metrics/tsdb/Series.d.ts +67 -0
  97. package/dist/types/telemetry/custom-implementations/metrics/tsdb/SeriesRegistry.d.ts +69 -0
  98. package/dist/types/telemetry/custom-implementations/metrics/tsdb/types.d.ts +68 -0
  99. package/dist/types/telemetry/custom-implementations/metrics/tsdb/utils.d.ts +21 -0
  100. package/dist/types/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.d.ts +2 -2
  101. package/dist/types/telemetry/custom-implementations/utils/storagePath.d.ts +12 -0
  102. package/dist/types/telemetry/custom-implementations/wrappers.d.ts +1 -1
  103. package/dist/types/telemetry/telemetryConfigurator.d.ts +1 -1
  104. package/dist/types/telemetry/telemetryRegistry.d.ts +8 -0
  105. package/dist/types/tlm-ai/agent.d.ts +1 -1
  106. package/dist/types/tlm-ai/aiService.d.ts +1 -1
  107. package/dist/types/tlm-log/logController.d.ts +2 -0
  108. package/dist/types/tlm-metric/metricsController.d.ts +16 -2
  109. package/dist/types/tlm-trace/traceController.d.ts +3 -1
  110. package/dist/types/types/index.d.ts +2 -2
  111. package/dist/ui/assets/{ApiDocsPage-C_VVPPHa.js → ApiDocsPage-BFUrXE5F.js} +2 -2
  112. package/dist/ui/assets/CollapsibleCard-STA1GVQO.js +1 -0
  113. package/dist/ui/assets/DevToolsPage-BRSfZqO_.js +1 -0
  114. package/dist/ui/assets/LandingPage-DzeDy7q7.js +6 -0
  115. package/dist/ui/assets/LogsPage-BeiFrV2X.js +1 -0
  116. package/dist/ui/assets/{NotFoundPage-B3quk3P1.js → NotFoundPage-fRNOatbM.js} +1 -1
  117. package/dist/ui/assets/PluginCreatePage-Ch_RXsdf.js +50 -0
  118. package/dist/ui/assets/PluginPage-Cl65ZZ_n.js +27 -0
  119. package/dist/ui/assets/TraceSpansPage-BoK4M5Hh.js +6 -0
  120. package/dist/ui/assets/VirtualizedListPanel-zcj0v7DL.js +16 -0
  121. package/dist/ui/assets/alert-BkNVKxJN.js +1133 -0
  122. package/dist/ui/assets/badge-CN7FeufU.js +1 -0
  123. package/dist/ui/assets/{chevron-down-CPsvsmqj.js → chevron-down-CG--ounh.js} +1 -1
  124. package/dist/ui/assets/{chevron-up-Df9jMo1X.js → chevron-up-B6tzMAOm.js} +1 -1
  125. package/dist/ui/assets/{circle-alert-DOPQPvU8.js → circle-alert-BDF8Tq9y.js} +1 -1
  126. package/dist/ui/assets/dialog-BrpWNk36.js +15 -0
  127. package/dist/ui/assets/index-6xOVKwKn.js +305 -0
  128. package/dist/ui/assets/index-D6f1KjWV.css +1 -0
  129. package/dist/ui/assets/index-D96rVSkR.js +1 -0
  130. package/dist/ui/assets/info-99kuqpbx.js +6 -0
  131. package/dist/ui/assets/{input-Dzvg_ZEZ.js → input-B-01QDg_.js} +1 -1
  132. package/dist/ui/assets/label-CQLeZjM1.js +1 -0
  133. package/dist/ui/assets/{loader-circle-CrvlRy5o.js → loader-circle-BoDGk-BO.js} +1 -1
  134. package/dist/ui/assets/{loginPage-qa4V-B70.js → loginPage-8F4EEd1B.js} +1 -1
  135. package/dist/ui/assets/metrics-page-D1GxaB_c.css +1 -0
  136. package/dist/ui/assets/metrics-page-DPtteXqY.js +31 -0
  137. package/dist/ui/assets/popover-DS_8DYYt.js +11 -0
  138. package/dist/ui/assets/select-DYjegiXi.js +6 -0
  139. package/dist/ui/assets/separator-DGsRxIrl.js +6 -0
  140. package/dist/ui/assets/severityOptions-DEOvJqC9.js +11 -0
  141. package/dist/ui/assets/square-pen-DPhgYz6O.js +6 -0
  142. package/dist/ui/assets/switch-Di9NJH2A.js +1 -0
  143. package/dist/ui/assets/trace-DJq1miYa.js +1 -0
  144. package/dist/ui/assets/upload-BiLTpCnX.js +11 -0
  145. package/dist/ui/assets/{utilService-DNyqzwj0.js → utilService-CNZOmadC.js} +1 -1
  146. package/dist/ui/assets/wand-sparkles-CPoBNFFg.js +6 -0
  147. package/dist/ui/index.html +2 -2
  148. package/package.json +44 -48
  149. package/dist/ui/assets/CollapsibleCard-B3KR_8mL.js +0 -1
  150. package/dist/ui/assets/DevToolsPage-OyZcDcmw.js +0 -1
  151. package/dist/ui/assets/LandingPage-CppFBA6K.js +0 -6
  152. package/dist/ui/assets/LogsPage-9Fq8GArS.js +0 -26
  153. package/dist/ui/assets/PluginCreatePage-X_aCH4t4.js +0 -50
  154. package/dist/ui/assets/PluginPage-DMDSihrZ.js +0 -27
  155. package/dist/ui/assets/alert-jQ9HCPIf.js +0 -1133
  156. package/dist/ui/assets/badge-CNq0-mH5.js +0 -1
  157. package/dist/ui/assets/card-DFAwwhN3.js +0 -1
  158. package/dist/ui/assets/index-BkD6DijD.js +0 -15
  159. package/dist/ui/assets/index-CERGVYZK.js +0 -292
  160. package/dist/ui/assets/index-CSIPf9qw.css +0 -1
  161. package/dist/ui/assets/label-DuVnkZ4q.js +0 -1
  162. package/dist/ui/assets/select-DhS8YUtJ.js +0 -1
  163. package/dist/ui/assets/separator-isK4chBP.js +0 -6
  164. package/dist/ui/assets/severityOptions-O38dSOfk.js +0 -11
  165. package/dist/ui/assets/switch-Z3mImG9n.js +0 -1
  166. package/dist/ui/assets/tabs-_77MUUQe.js +0 -16
  167. package/dist/ui/assets/upload-C1LT4Gkb.js +0 -16
@@ -31,8 +31,8 @@ servers:
31
31
  port:
32
32
  default: "3000"
33
33
  basePath:
34
- default: telemetry
35
- description: Base path for all endpoints. Can be customized, e.g. "myCustomTelemetryPath"
34
+ default: oas-telemetry
35
+ description: Base path for all endpoints. Configured via OASTLM_BOOT_BASE_URL environment variable
36
36
 
37
37
  tags:
38
38
  - name: traces
@@ -326,6 +326,44 @@ paths:
326
326
  type: string
327
327
  example: Invalid retention time. Must be a positive number.
328
328
 
329
+ /traces/export:
330
+ get:
331
+ tags:
332
+ - traces
333
+ summary: Export collected traces
334
+ description: Export all collected traces in NDJSON format
335
+ responses:
336
+ '200':
337
+ description: Traces exported successfully
338
+ content:
339
+ application/x-ndjson:
340
+ schema:
341
+ type: string
342
+
343
+ /traces/import:
344
+ post:
345
+ tags:
346
+ - traces
347
+ summary: Import traces
348
+ description: Import traces from NDJSON format
349
+ requestBody:
350
+ required: true
351
+ content:
352
+ application/x-ndjson:
353
+ schema:
354
+ type: string
355
+ responses:
356
+ '200':
357
+ description: Traces imported successfully
358
+ content:
359
+ application/json:
360
+ schema:
361
+ type: object
362
+ properties:
363
+ message:
364
+ type: string
365
+ example: Traces imported successfully
366
+
329
367
  /metrics:
330
368
  get:
331
369
  summary: Get collected metrics
@@ -548,6 +586,69 @@ paths:
548
586
  type: string
549
587
  example: Invalid retention time. Must be a positive number.
550
588
 
589
+ /metrics/export:
590
+ get:
591
+ tags:
592
+ - metrics
593
+ summary: Export collected metrics
594
+ description: Export all collected metrics in NDJSON format
595
+ responses:
596
+ '200':
597
+ description: Metrics exported successfully
598
+ content:
599
+ application/x-ndjson:
600
+ schema:
601
+ type: string
602
+
603
+ /metrics/import:
604
+ post:
605
+ tags:
606
+ - metrics
607
+ summary: Import metrics
608
+ description: Import metrics from NDJSON format
609
+ requestBody:
610
+ required: true
611
+ content:
612
+ application/x-ndjson:
613
+ schema:
614
+ type: string
615
+ responses:
616
+ '200':
617
+ description: Metrics imported successfully
618
+ content:
619
+ application/json:
620
+ schema:
621
+ type: object
622
+ properties:
623
+ message:
624
+ type: string
625
+ example: Metrics imported successfully
626
+
627
+ /metrics/stats:
628
+ get:
629
+ tags:
630
+ - metrics
631
+ summary: Get metrics statistics
632
+ description: Get statistics about collected metrics
633
+ responses:
634
+ '200':
635
+ description: Metrics statistics retrieved successfully
636
+ content:
637
+ application/json:
638
+ schema:
639
+ type: object
640
+ properties:
641
+ totalMetrics:
642
+ type: number
643
+ example: 42
644
+ activeMetrics:
645
+ type: number
646
+ example: 35
647
+ retentionTime:
648
+ type: number
649
+ description: Retention time in seconds
650
+ example: 3600
651
+
551
652
  /logs:
552
653
  get:
553
654
  summary: Get collected logs
@@ -683,6 +784,44 @@ paths:
683
784
  type: string
684
785
  example: Invalid regex in query
685
786
 
787
+ /logs/export:
788
+ get:
789
+ tags:
790
+ - logs
791
+ summary: Export collected logs
792
+ description: Export all collected logs in NDJSON format
793
+ responses:
794
+ '200':
795
+ description: Logs exported successfully
796
+ content:
797
+ application/x-ndjson:
798
+ schema:
799
+ type: string
800
+
801
+ /logs/import:
802
+ post:
803
+ tags:
804
+ - logs
805
+ summary: Import logs
806
+ description: Import logs from NDJSON format
807
+ requestBody:
808
+ required: true
809
+ content:
810
+ application/x-ndjson:
811
+ schema:
812
+ type: string
813
+ responses:
814
+ '200':
815
+ description: Logs imported successfully
816
+ content:
817
+ application/json:
818
+ schema:
819
+ type: object
820
+ properties:
821
+ message:
822
+ type: string
823
+ example: Logs imported successfully
824
+
686
825
  /utils/spec:
687
826
  get:
688
827
  summary: Get OpenAPI spec in JSON
@@ -842,14 +981,14 @@ paths:
842
981
  type: string
843
982
  example: Started generating mock logs
844
983
 
845
- /utils/generate-wait/{seconds}:
984
+ /utils/generate-wait:
846
985
  get:
847
986
  summary: Wait for the specified seconds
848
987
  tags:
849
988
  - utils
850
989
  parameters:
851
990
  - name: seconds
852
- in: path
991
+ in: query
853
992
  required: false
854
993
  schema:
855
994
  type: integer
@@ -867,6 +1006,21 @@ paths:
867
1006
  type: number
868
1007
  example: 2
869
1008
 
1009
+ /utils/oas-telemetry-spec:
1010
+ get:
1011
+ summary: Get OAS-Telemetry specification
1012
+ tags:
1013
+ - utils
1014
+ description: Retrieve the OpenAPI specification for OAS-Telemetry
1015
+ responses:
1016
+ '200':
1017
+ description: OK
1018
+ content:
1019
+ application/json:
1020
+ schema:
1021
+ type: object
1022
+ description: OpenAPI specification
1023
+
870
1024
  /health:
871
1025
  get:
872
1026
  summary: Health check
package/dist/esm/index.js CHANGED
@@ -5,25 +5,26 @@ import { getConfig } from './config/config.js';
5
5
  import { Router } from 'express';
6
6
  import { configureRoutes } from './routesManager.js';
7
7
  import { configureTelemetry } from './telemetry/telemetryConfigurator.js';
8
+ import { isTelemetryConfigured, setTelemetryRouter, getTelemetryRouter } from './telemetry/telemetryRegistry.js';
8
9
  import { bootEnvVariables } from "./config/bootConfig.js";
9
- // WARN: If changed the API, also change in packages/lib/src/types/cjs-index.d.ts (used for CJS compilation)
10
10
  /**
11
11
  * Returns the OAS-Telemetry middleware.
12
12
  * All parameters are optional. However, either `spec` or `specFileName` must be provided to enable endpoint filtering.
13
13
  */
14
- export default function oasTelemetry(oasTlmInputConfig) {
14
+ function oasTelemetry(oasTlmInputConfig) {
15
+ if (isTelemetryConfigured()) {
16
+ return getTelemetryRouter();
17
+ }
15
18
  const router = Router();
16
- // This environment variable cannot be set via the config object,
17
- // as it is required to disable OpenTelemetry SDK initialization,
18
- // which occurs during the first import at the top of this file.
19
19
  if (bootEnvVariables.OASTLM_BOOT_MODULE_DISABLED) {
20
+ setTelemetryRouter(router);
20
21
  return router;
21
22
  }
22
23
  const oasTlmConfig = getConfig(oasTlmInputConfig);
23
- logger.info("BaseUrl: ", oasTlmConfig.general.baseUrl);
24
- if (!oasTlmConfig.general.spec && !oasTlmConfig.general.specFileName)
25
- logger.warn("No spec provided, endpoint filtering will not be available. Please provide either `spec` or `specFileName` in the configuration.");
24
+ logger.info("BaseUrl: ", bootEnvVariables.OASTLM_BOOT_BASE_URL);
26
25
  configureTelemetry(oasTlmConfig);
27
26
  configureRoutes(router, oasTlmConfig);
27
+ setTelemetryRouter(router);
28
28
  return router;
29
29
  }
30
+ export { oasTelemetry };
@@ -13,21 +13,17 @@ import { getAIRoutes } from "./tlm-ai/aiRoutes.js";
13
13
  import { bootEnvVariables } from "./config/bootConfig.js";
14
14
  import { getPluginRoutes } from "./tlm-plugin/pluginRoutes.js";
15
15
  export const configureRoutes = (router, oasTlmConfig) => {
16
+ if (!oasTlmConfig.general.spec && !oasTlmConfig.general.specFileName) {
17
+ logger.warn("No spec provided, endpoint filtering will not be available. Please provide either `spec` or `specFileName` in the configuration.");
18
+ }
16
19
  if (bootEnvVariables.OASTLM_BOOT_ENV === 'development') {
17
20
  logger.info("Running in development mode, enabling CORS for all origins");
18
21
  router.use(cors({
19
- origin: (origin, callback) => {
20
- if (!origin || /^http:\/\/localhost:\d+$/.test(origin)) {
21
- callback(null, true);
22
- }
23
- else {
24
- callback(new Error('Not allowed by CORS'));
25
- }
26
- },
22
+ origin: true,
27
23
  credentials: true
28
24
  }));
29
25
  }
30
- const telemetryBaseUrl = oasTlmConfig.general.baseUrl;
26
+ const telemetryBaseUrl = bootEnvVariables.OASTLM_BOOT_BASE_URL;
31
27
  // Sub-router for all telemetry endpoints
32
28
  const telemetryRouter = Router();
33
29
  // Body parser for JSON requests
@@ -35,7 +31,7 @@ export const configureRoutes = (router, oasTlmConfig) => {
35
31
  if (req.body !== undefined) {
36
32
  return next(); // Already parsed, no need to parse again.
37
33
  }
38
- return json({ limit: '10mb' })(req, res, next);
34
+ return json({ limit: '500mb' })(req, res, next);
39
35
  });
40
36
  telemetryRouter.get('/health', (_req, res) => {
41
37
  res.status(200).send({ status: 'OK' });
@@ -6,18 +6,36 @@ import { applyNesting, removeCircularRefs } from '../utils/circular.js';
6
6
  import { Enabler } from '../wrappers.js';
7
7
  import logger from '../../../utils/logger.js';
8
8
  import { pluginService } from '../../../tlm-plugin/pluginService.js';
9
+ import { getStoragePath } from '../utils/storagePath.js';
9
10
  export class InMemoryDbLogExporter extends Enabler {
11
+ _db = null;
12
+ _miniSearch = null;
13
+ _retentionTimeInSeconds;
14
+ _storagePath = null;
15
+ _initialized = false;
10
16
  constructor(retentionTimeInSeconds = 3600) {
11
17
  super();
12
18
  this._retentionTimeInSeconds = retentionTimeInSeconds;
13
- this._db = new Datastore({ timestampData: true });
19
+ this._storagePath = getStoragePath('logs');
20
+ this._startCleanupJob();
21
+ }
22
+ _ensureInitialized() {
23
+ if (this._initialized)
24
+ return;
25
+ this._initialized = true;
26
+ this._db = new Datastore(this._storagePath ? { filename: this._storagePath, timestampData: true, autoload: true } : { timestampData: true });
14
27
  this._db.ensureIndex({ fieldName: 'createdAt' });
15
28
  this._miniSearch = new MiniSearch({
16
29
  fields: ['body'],
17
30
  storeFields: ['_id'],
18
31
  idField: '_id',
19
32
  });
20
- this._startCleanupJob();
33
+ if (this._storagePath) {
34
+ logger.info(`[LogExporter] Disk storage enabled at: ${this._storagePath}`);
35
+ }
36
+ else {
37
+ logger.info(`[LogExporter] Using in-memory storage`);
38
+ }
21
39
  }
22
40
  /*
23
41
  * SUPER WARNING:
@@ -31,6 +49,7 @@ export class InMemoryDbLogExporter extends Enabler {
31
49
  * @param resultCallback
32
50
  */
33
51
  export(logs, resultCallback) {
52
+ this._ensureInitialized();
34
53
  const logsToInsert = logs.map(logRecord => {
35
54
  // Remove circular references first, then apply nesting, then export info
36
55
  const formattedLog = this._formatLogRecord(logRecord);
@@ -48,7 +67,17 @@ export class InMemoryDbLogExporter extends Enabler {
48
67
  resultCallback({ code: ExportResultCode.SUCCESS });
49
68
  }
50
69
  reset() {
51
- this._db = new Datastore();
70
+ this._ensureInitialized();
71
+ // Remove all logs from database but keep persistence enabled
72
+ this._db.remove({}, { multi: true }, (err) => {
73
+ if (err) {
74
+ logger.error(`[LogExporter] Error during reset: ${err.message}`);
75
+ }
76
+ else {
77
+ logger.info(`[LogExporter] Reset - all logs cleared`);
78
+ }
79
+ });
80
+ // Clear mini search index
52
81
  this._miniSearch = new MiniSearch({
53
82
  fields: ['body'],
54
83
  storeFields: ['_id'],
@@ -61,8 +90,10 @@ export class InMemoryDbLogExporter extends Enabler {
61
90
  async shutdown() {
62
91
  this._db = null;
63
92
  this._miniSearch = null;
93
+ this._initialized = false;
64
94
  }
65
95
  async find(findConfig) {
96
+ this._ensureInitialized();
66
97
  const { query, messageSearch, limit, sortOrder } = findConfig;
67
98
  const finalQuery = { ...query };
68
99
  const effectiveSortOrder = sortOrder || { timestamp: -1 };
@@ -73,10 +104,13 @@ export class InMemoryDbLogExporter extends Enabler {
73
104
  finalQuery._id = { $in: ids };
74
105
  }
75
106
  const docs = await new Promise((resolve, reject) => {
76
- this._db.find(finalQuery)
77
- .sort(effectiveSortOrder)
78
- .limit(limit)
79
- .exec((err, docs) => {
107
+ let query_exec = this._db.find(finalQuery)
108
+ .sort(effectiveSortOrder);
109
+ // Only apply limit if provided
110
+ if (limit !== undefined) {
111
+ query_exec = query_exec.limit(limit);
112
+ }
113
+ query_exec.exec((err, docs) => {
80
114
  if (err)
81
115
  reject(err);
82
116
  else
@@ -103,6 +137,7 @@ export class InMemoryDbLogExporter extends Enabler {
103
137
  });
104
138
  }
105
139
  getFinishedLogs() {
140
+ this._ensureInitialized();
106
141
  return this._db.getAllData();
107
142
  }
108
143
  /**
@@ -136,6 +171,8 @@ export class InMemoryDbLogExporter extends Enabler {
136
171
  return this._retentionTimeInSeconds;
137
172
  }
138
173
  _insertLogs(logsToInsert, resultCallback) {
174
+ if (!this._db)
175
+ return resultCallback({ code: ExportResultCode.FAILED });
139
176
  this._db.insert(logsToInsert, (err, newDocs) => {
140
177
  if (err) {
141
178
  console.dir(err);
@@ -143,7 +180,7 @@ export class InMemoryDbLogExporter extends Enabler {
143
180
  return;
144
181
  }
145
182
  // console.dir(newDocs, { depth: 3 });
146
- newDocs.forEach((doc) => this._miniSearch.add(doc));
183
+ newDocs.forEach((doc) => this._miniSearch?.add(doc));
147
184
  resultCallback({ code: ExportResultCode.SUCCESS });
148
185
  });
149
186
  return;
@@ -151,6 +188,8 @@ export class InMemoryDbLogExporter extends Enabler {
151
188
  _startCleanupJob() {
152
189
  const interval = 1000;
153
190
  setInterval(() => {
191
+ if (!this._db)
192
+ return; // Safety check
154
193
  const expirationDate = new Date(Date.now() - this._retentionTimeInSeconds * 1000);
155
194
  this._db.remove({ createdAt: { $lt: expirationDate } }, { multi: true }, (err, numRemoved) => {
156
195
  if (err) {
@@ -1,32 +1,100 @@
1
1
  import { ExportResultCode } from '@opentelemetry/core';
2
- import dataStore from '@seald-io/nedb';
3
- import { applyNesting } from '../utils/circular.js';
4
2
  import { Enabler } from '../wrappers.js';
5
3
  import logger from '../../../utils/logger.js';
6
4
  import { pluginService } from '../../../tlm-plugin/pluginService.js';
5
+ import { rawToOtel } from '../metrics/tsdb/utils.js';
6
+ import { SeriesRegistry } from '../metrics/tsdb/SeriesRegistry.js';
7
+ import { getStoragePath } from '../utils/storagePath.js';
8
+ import fs from 'fs';
9
+ /**
10
+ * In-Memory TSDB Metric Exporter
11
+ * Series identified by: scope + metricName + attributes
12
+ */
7
13
  export class InMemoryDbMetricExporter extends Enabler {
8
- constructor(retentionTimeInSeconds = 3600) {
14
+ static DEFAULT_CONFIG = {
15
+ retentionTimeInSeconds: 3600,
16
+ chunkSize: 120,
17
+ maxChunks: 60
18
+ };
19
+ registry;
20
+ config;
21
+ cachedResource = null;
22
+ _storageFilePath = null;
23
+ _autoSaveInterval = null;
24
+ _storageLogged = false; // Track if we've logged storage info
25
+ get rawDataDB() {
26
+ // For debug/inspection only - metrics are stored in registry chunks
27
+ const stats = this.registry.getStats();
28
+ return stats.totalSamples > 0 ? [`[${stats.totalSamples} samples in registry]`] : [];
29
+ }
30
+ constructor(config) {
9
31
  super();
10
- this._retentionTimeInSeconds = retentionTimeInSeconds;
11
- this._metrics = new dataStore({ timestampData: true });
12
- this._metrics.ensureIndex({ fieldName: 'createdAt' });
32
+ this.config = { ...InMemoryDbMetricExporter.DEFAULT_CONFIG, ...config };
33
+ this.registry = new SeriesRegistry(this.config.chunkSize, this.config.maxChunks);
34
+ // Load persisted metrics if disk storage is enabled
35
+ this._storageFilePath = getStoragePath('metrics');
36
+ if (this._storageFilePath) {
37
+ this._loadMetricsFromDisk();
38
+ this._startAutoSave();
39
+ }
13
40
  this._startCleanupJob();
14
41
  }
15
- export(metrics, resultCallback) {
42
+ _loadMetricsFromDisk() {
43
+ if (!this._storageFilePath)
44
+ return;
45
+ try {
46
+ if (fs.existsSync(this._storageFilePath)) {
47
+ const ndjsonData = fs.readFileSync(this._storageFilePath, 'utf-8');
48
+ this.registry.deserializeFromNDJSON(ndjsonData);
49
+ }
50
+ }
51
+ catch {
52
+ // Silently fail during boot
53
+ }
54
+ }
55
+ _startAutoSave() {
56
+ if (!this._storageFilePath)
57
+ return;
58
+ // Save every 30 seconds
59
+ this._autoSaveInterval = setInterval(() => {
60
+ this._saveMetricsToDisk();
61
+ }, 30000);
62
+ }
63
+ _saveMetricsToDisk() {
64
+ if (!this._storageFilePath)
65
+ return;
66
+ try {
67
+ const ndjsonData = this.registry.serializeToNDJSON();
68
+ fs.writeFileSync(this._storageFilePath, ndjsonData);
69
+ }
70
+ catch {
71
+ // Silently fail to avoid logging issues
72
+ }
73
+ }
74
+ export(resourceMetrics, resultCallback) {
75
+ // Log storage info on first export (when logger is ready)
76
+ if (!this._storageLogged) {
77
+ this._storageLogged = true;
78
+ if (this._storageFilePath) {
79
+ logger.info(`[MetricExporter] Disk storage enabled at: ${this._storageFilePath} (auto-save every 30s)`);
80
+ }
81
+ else {
82
+ logger.info(`[MetricExporter] Using in-memory storage`);
83
+ }
84
+ }
16
85
  try {
17
- const scopeMetrics = metrics?.scopeMetrics;
18
- const cleanMetrics = applyNesting(scopeMetrics);
19
- cleanMetrics.forEach((metric) => {
86
+ // Cache resource (unique per exporter instance)
87
+ if (!this.cachedResource) {
88
+ this.cachedResource = resourceMetrics.resource;
89
+ }
90
+ const scopeMetrics = resourceMetrics.scopeMetrics;
91
+ // Broadcast to plugins
92
+ scopeMetrics?.forEach((metric) => {
20
93
  pluginService.broadcastMetric(metric);
21
94
  });
22
- // Insert only if exporter is enabled
23
- if (this.isEnabled()) {
24
- this._metrics.insert(cleanMetrics, (err, _newDoc) => {
25
- if (err) {
26
- logger.error('Insertion Error:', err);
27
- return;
28
- }
29
- });
95
+ // Store if enabled - use new storeScopeMetrics method
96
+ if (this.isEnabled() && scopeMetrics) {
97
+ this.registry.storeScopeMetrics(scopeMetrics);
30
98
  }
31
99
  setTimeout(() => resultCallback({ code: ExportResultCode.SUCCESS }), 0);
32
100
  }
@@ -40,48 +108,96 @@ export class InMemoryDbMetricExporter extends Enabler {
40
108
  }
41
109
  shutdown() {
42
110
  this._enabled = false;
43
- this._metrics = new dataStore();
111
+ // Stop auto-save and save one final time
112
+ if (this._autoSaveInterval) {
113
+ clearInterval(this._autoSaveInterval);
114
+ this._autoSaveInterval = null;
115
+ }
116
+ if (this._storageFilePath) {
117
+ this._saveMetricsToDisk();
118
+ logger.info(`[MetricExporter] Metrics saved to disk at shutdown`);
119
+ }
120
+ this.registry.reset();
44
121
  return this.forceFlush();
45
122
  }
123
+ reset() {
124
+ // Clear metrics but save empty state to disk
125
+ this.registry.reset();
126
+ if (this._storageFilePath) {
127
+ this._saveMetricsToDisk();
128
+ logger.info(`[MetricExporter] Reset - all metrics cleared and saved to disk`);
129
+ }
130
+ else {
131
+ logger.info(`[MetricExporter] Reset - all metrics cleared`);
132
+ }
133
+ }
46
134
  forceFlush() {
47
135
  return Promise.resolve();
48
136
  }
49
- find(search, callback) {
50
- this._metrics.find(search, callback);
137
+ getCachedResource() {
138
+ return this.cachedResource;
51
139
  }
52
- reset() {
53
- this._metrics = new dataStore();
140
+ set retentionTimeInSeconds(value) {
141
+ this.config.retentionTimeInSeconds = value;
142
+ logger.info(`Retention time set to ${value} seconds`);
143
+ }
144
+ get retentionTimeInSeconds() {
145
+ return this.config.retentionTimeInSeconds;
54
146
  }
55
- getFinishedMetrics() {
56
- return this._metrics.getAllData();
147
+ getStats() {
148
+ return this.registry.getStats();
149
+ }
150
+ _startCleanupJob() {
151
+ setInterval(() => {
152
+ const retentionTimeNs = this.config.retentionTimeInSeconds * 1_000_000_000;
153
+ const result = this.registry.evictOldData(retentionTimeNs);
154
+ if (result.evictedChunks > 0 || result.evictedSeries > 0) {
155
+ logger.debug(`Cleanup: evicted ${result.evictedChunks} chunks, ${result.evictedSeries} series`);
156
+ }
157
+ }, 5000);
57
158
  }
58
159
  /**
59
- * Inserts metrics into the in-memory database.
60
- * @param metrics - The metrics to insert.
61
- * @param callback - The callback to execute after insertion.
160
+ * Find metrics by scope+metric queries with filters
161
+ * Supports both raw and otel formats
62
162
  */
63
- insert(metrics, callback) {
64
- this._metrics.insert(metrics, callback);
163
+ find(request) {
164
+ const format = request.format || 'raw';
165
+ // Get raw results from registry using new unified query method
166
+ const rawResults = this.registry.query(request.scopeMetrics, request.from, request.to);
167
+ // Convert format if needed
168
+ if (format === 'otel') {
169
+ return { results: rawToOtel(rawResults) };
170
+ }
171
+ return { results: rawResults };
65
172
  }
66
- set retentionTimeInSeconds(retentionTimeInSeconds) {
67
- this._retentionTimeInSeconds = retentionTimeInSeconds;
68
- logger.info(`InMemoryDbMetricExporter retention time set to ${this._retentionTimeInSeconds} seconds`);
173
+ /**
174
+ * Export all metrics to NDJSON format (one chunk per line)
175
+ */
176
+ exportToNDJSON() {
177
+ return this.registry.serializeToNDJSON();
69
178
  }
70
- get retentionTimeInSeconds() {
71
- return this._retentionTimeInSeconds;
179
+ /**
180
+ * Import metrics from NDJSON format
181
+ */
182
+ importFromNDJSON(ndjsonData) {
183
+ this.registry.deserializeFromNDJSON(ndjsonData);
72
184
  }
73
- _startCleanupJob() {
74
- const interval = 1000;
75
- setInterval(() => {
76
- const expirationDate = new Date(Date.now() - this._retentionTimeInSeconds * 1000);
77
- this._metrics.remove({ createdAt: { $lt: expirationDate } }, { multi: true }, (err, numRemoved) => {
78
- if (err) {
79
- logger.error('Error in TTL cleanup:', err);
80
- }
81
- else if (numRemoved > 0) {
82
- logger.debug(`TTL cleanup: removed ${numRemoved} expired metrics`);
83
- }
84
- });
85
- }, interval);
185
+ /**
186
+ * Insert metrics in OpenTelemetry (OTEL) format directly into the registry
187
+ * @param scopeMetrics Array of ScopeMetrics (OTEL format)
188
+ */
189
+ insertOtel(scopeMetrics) {
190
+ // Store only in registry (chunks) - no duplication
191
+ if (this.isEnabled()) {
192
+ this.registry.storeScopeMetrics(scopeMetrics);
193
+ }
194
+ }
195
+ /**
196
+ * Insert metrics in raw format (MetricQueryResult[]), converts to OTEL and delegates to insertOtel
197
+ * @param rawScopeMetrics Array of MetricQueryResult (raw format)
198
+ */
199
+ insertRaw(rawScopeMetrics) {
200
+ const otelScopeMetrics = rawToOtel(rawScopeMetrics);
201
+ this.insertOtel(otelScopeMetrics);
86
202
  }
87
203
  }