@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
@@ -1,70 +1,64 @@
1
1
  import { inMemoryDbMetricExporter } from '../telemetry/telemetryRegistry.js';
2
- import { convertRegexRecursively } from '../utils/regexUtils.js';
3
- export const listMetrics = async (req, res) => {
2
+ /**
3
+ * Parse NDJSON import data
4
+ * Each line must be a valid JSON object
5
+ * @param body - Request body (raw string)
6
+ * @returns Array of metric objects
7
+ */
8
+ function parseImportData(body) {
9
+ if (typeof body !== 'string') {
10
+ throw new Error('Import must be NDJSON format (plain text with one JSON object per line)');
11
+ }
12
+ const lines = body.split('\n').filter((line) => line.trim());
13
+ return lines.map((line, index) => {
14
+ try {
15
+ return JSON.parse(line);
16
+ }
17
+ catch {
18
+ console.error(`Failed to parse NDJSON line ${index + 1}: ${line}`);
19
+ throw new Error(`Invalid JSON on line ${index + 1}`);
20
+ }
21
+ });
22
+ }
23
+ export const getMetricsStats = async (req, res) => {
4
24
  try {
5
- const metrics = inMemoryDbMetricExporter.getFinishedMetrics();
6
- res.send({ metricsCount: metrics.length, metrics: metrics });
25
+ const stats = inMemoryDbMetricExporter.getStats();
26
+ res.send(stats);
7
27
  }
8
28
  catch (err) {
9
29
  console.error(err);
10
- res.status(500).send({ error: 'Failed to list metrics data' });
11
- }
12
- };
13
- export const findMetrics = (req, res) => {
14
- const body = req.body;
15
- const query = body?.query ? body.query : {};
16
- let processedQuery;
17
- try {
18
- processedQuery = convertRegexRecursively(query);
30
+ res.status(500).send({ error: 'Failed to get metrics stats' });
19
31
  }
20
- catch (error) {
21
- console.error(error.message);
22
- res.status(400).send({ error: error.message });
23
- return; // Exit if invalid regex was encountered
24
- }
25
- inMemoryDbMetricExporter.find(processedQuery, (err, docs) => {
26
- if (err) {
27
- console.error(err);
28
- res.status(404).send({ metricsCount: 0, metrics: [], error: err });
29
- return;
30
- }
31
- const metrics = docs;
32
- res.send({ metricsCount: metrics.length, metrics: metrics });
33
- });
34
32
  };
35
33
  export const resetMetrics = (req, res) => {
36
34
  inMemoryDbMetricExporter.reset();
37
35
  res.send('Metrics reset');
38
36
  };
39
37
  export const insertMetricsToDb = async (req, res) => {
40
- const jsonContent = req.body.metrics;
41
- const resetData = req.query.reset === 'true';
42
- if (!Array.isArray(jsonContent)) {
43
- res.status(400).send({ error: 'Invalid data format.' });
44
- return;
45
- }
46
- const cleanedMetrics = jsonContent.map((metric) => {
47
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
48
- const { _id, ...rest } = metric; // Remove _id if it exists
49
- return rest; // Return the cleaned metric object
50
- });
51
38
  try {
39
+ const scopeMetricsData = (req.body || {}).scopeMetrics;
40
+ const resetData = req.query.reset === 'true';
41
+ const format = (req.body.format || req.query.format || 'raw');
42
+ if (!Array.isArray(scopeMetricsData)) {
43
+ res.status(400).send({ error: 'Invalid data format. Expected array in body.scopeMetrics' });
44
+ return;
45
+ }
52
46
  let message = '';
53
47
  if (resetData) {
54
48
  inMemoryDbMetricExporter.reset();
55
49
  message += 'Metrics Database reset. ';
56
50
  }
57
- await new Promise((resolve, reject) => {
58
- inMemoryDbMetricExporter.insert(cleanedMetrics, (err, newDocs) => {
59
- if (err) {
60
- console.error('Error inserting metrics:', err);
61
- return reject(err);
62
- }
63
- resolve(newDocs);
64
- });
51
+ // Store samples
52
+ if (format === 'otel')
53
+ inMemoryDbMetricExporter.insertOtel(scopeMetricsData);
54
+ else
55
+ inMemoryDbMetricExporter.insertRaw(scopeMetricsData);
56
+ message += `Inserted ${scopeMetricsData.length} scopeMetrics (format: ${format}).`;
57
+ res.send({
58
+ message,
59
+ scopeMetricsCount: scopeMetricsData.length,
60
+ format
65
61
  });
66
- message += `Inserted ${cleanedMetrics.length} metrics.`;
67
- res.send({ message, InsertedMetricsCount: cleanedMetrics.length });
68
62
  }
69
63
  catch (err) {
70
64
  console.error(err);
@@ -84,7 +78,7 @@ export const statusMetrics = (req, res) => {
84
78
  res.send({ active: isRunning });
85
79
  };
86
80
  export const setMetricRetentionTime = (req, res) => {
87
- const retentionTimeInSeconds = req.body.retentionTimeInSeconds;
81
+ const retentionTimeInSeconds = (req.body || {}).retentionTimeInSeconds;
88
82
  if (typeof retentionTimeInSeconds !== 'number' || retentionTimeInSeconds <= 0) {
89
83
  res.status(400).send({ error: 'Invalid retention time. Must be a positive number.' });
90
84
  return;
@@ -96,3 +90,132 @@ export const getMetricRetentionTime = (req, res) => {
96
90
  const retentionTimeInSeconds = inMemoryDbMetricExporter.retentionTimeInSeconds || 0;
97
91
  res.send({ retentionTimeInSeconds: retentionTimeInSeconds });
98
92
  };
93
+ /**
94
+ * Find metrics by scope+metric queries with filters (POST /metrics/find or GET /metrics)
95
+ * All parameters are optional:
96
+ * - If scopeMetrics is not provided or empty, returns all metrics
97
+ * - If startTime/endTime not provided, returns all available data
98
+ * - format can be 'raw' (default) or 'otel'
99
+ */
100
+ export const findMetrics = async (req, res) => {
101
+ try {
102
+ const { scopeMetrics, from, to, format } = req.body || {};
103
+ // Validate scopeMetrics structure if provided
104
+ if (scopeMetrics && Array.isArray(scopeMetrics)) {
105
+ for (const query of scopeMetrics) {
106
+ if (!query.scope || !query.scope.name || !query.descriptor || !query.descriptor.name) {
107
+ res.status(400).json({
108
+ error: 'Each query must have scope.name and descriptor.name defined'
109
+ });
110
+ return;
111
+ }
112
+ }
113
+ }
114
+ // Execute query
115
+ const response = inMemoryDbMetricExporter.find({
116
+ scopeMetrics: scopeMetrics && scopeMetrics.length > 0 ? scopeMetrics : undefined,
117
+ from,
118
+ to,
119
+ format: format || 'raw'
120
+ });
121
+ res.json({
122
+ format: format || 'raw',
123
+ scopeMetricsCount: response.results.length,
124
+ scopeMetrics: response.results,
125
+ });
126
+ }
127
+ catch (err) {
128
+ console.error(err);
129
+ res.status(500).json({ error: 'Failed to find metrics' });
130
+ }
131
+ };
132
+ /**
133
+ * Check data consistency between rawDataDB and queried data
134
+ * Compares raw exports with findMetrics(format=otel) without filters
135
+ */
136
+ export const checkMetricsConsistency = async (req, res) => {
137
+ try {
138
+ // Get raw exported data
139
+ const rawData = inMemoryDbMetricExporter.rawDataDB;
140
+ // Query all data with OTEL format
141
+ const response = inMemoryDbMetricExporter.find({
142
+ format: 'otel'
143
+ });
144
+ const queriedData = response.results;
145
+ // Compare counts
146
+ const statusCheck = rawData.length === queriedData.length;
147
+ res.json({
148
+ statusCheck,
149
+ raw: rawData,
150
+ queried: queriedData
151
+ });
152
+ }
153
+ catch (err) {
154
+ console.error(err);
155
+ res.status(500).json({ error: 'Failed to check metrics consistency' });
156
+ }
157
+ };
158
+ export const exportMetrics = (req, res) => {
159
+ try {
160
+ const ndjsonData = inMemoryDbMetricExporter.exportToNDJSON();
161
+ const timestamp = new Date().toISOString().slice(0, 19).replace(/[-T:]/g, '');
162
+ res.setHeader('Content-Type', 'application/x-ndjson');
163
+ res.setHeader('Content-Disposition', `attachment; filename="metrics-${timestamp}.ndjson"`);
164
+ res.setHeader('Transfer-Encoding', 'chunked');
165
+ res.write(ndjsonData);
166
+ res.end();
167
+ }
168
+ catch (err) {
169
+ console.error('Failed to export metrics:', err);
170
+ res.status(500).send({ error: 'Failed to export metrics', details: err.message });
171
+ }
172
+ };
173
+ export const importMetrics = async (req, res) => {
174
+ const resetData = req.query.reset === 'true';
175
+ const format = (req.query.format || 'raw');
176
+ try {
177
+ const ndjsonContent = req.body;
178
+ // Check if this is an exported NDJSON with headers (from our export function)
179
+ const firstLine = ndjsonContent.split('\n')[0];
180
+ let isExportedFormat = false;
181
+ try {
182
+ const firstObj = JSON.parse(firstLine);
183
+ isExportedFormat = firstObj.type === 'header';
184
+ }
185
+ catch {
186
+ // Not a valid JSON line, continue with normal parsing
187
+ }
188
+ let message = '';
189
+ if (resetData) {
190
+ inMemoryDbMetricExporter.reset();
191
+ message += 'Metrics Database reset. ';
192
+ }
193
+ // If it's an exported format with headers/metadata, use direct NDJSON import
194
+ if (isExportedFormat) {
195
+ inMemoryDbMetricExporter.importFromNDJSON(ndjsonContent);
196
+ message += `Imported metrics from exported NDJSON format.`;
197
+ res.send({ message, format: 'exported-ndjson' });
198
+ }
199
+ else {
200
+ // Otherwise parse as raw metrics (queryresults or OTEL format)
201
+ const metricsArray = parseImportData(ndjsonContent);
202
+ if (metricsArray.length === 0) {
203
+ res.status(400).send({ error: 'No valid metrics found in import data' });
204
+ return;
205
+ }
206
+ // Use the same insert logic as insertMetricsToDb, respecting raw vs otel format
207
+ if (format === 'otel') {
208
+ inMemoryDbMetricExporter.insertOtel(metricsArray);
209
+ }
210
+ else {
211
+ inMemoryDbMetricExporter.insertRaw(metricsArray);
212
+ }
213
+ message += `Imported ${metricsArray.length} metrics (format: ${format}).`;
214
+ res.send({ message, ImportedMetricsCount: metricsArray.length, format });
215
+ }
216
+ }
217
+ catch (err) {
218
+ console.error('Import failed:', err);
219
+ res.status(400).send({ error: 'Failed to import metrics', details: err.message });
220
+ }
221
+ };
@@ -1,5 +1,5 @@
1
- import { Router } from 'express';
2
- import { listMetrics, findMetrics, resetMetrics, insertMetricsToDb, startMetrics, stopMetrics, statusMetrics, setMetricRetentionTime, getMetricRetentionTime } from './metricsController.js';
1
+ import { Router, text } from 'express';
2
+ import { resetMetrics, insertMetricsToDb, startMetrics, stopMetrics, statusMetrics, setMetricRetentionTime, getMetricRetentionTime, getMetricsStats, findMetrics, exportMetrics, importMetrics, } from './metricsController.js';
3
3
  export const getMetricsRoutes = () => {
4
4
  const router = Router();
5
5
  // Metrics Control
@@ -9,9 +9,15 @@ export const getMetricsRoutes = () => {
9
9
  router.post('/reset', resetMetrics);
10
10
  router.post('/retention-time', setMetricRetentionTime);
11
11
  router.get('/retention-time', getMetricRetentionTime);
12
- router.get('/', listMetrics);
13
- router.post('/', insertMetricsToDb);
12
+ // Export/Import
13
+ router.get('/export', exportMetrics);
14
+ // Use text middleware for import to handle NDJSON format
15
+ router.post('/import', text({ type: 'application/x-ndjson', limit: '500mb' }), importMetrics);
16
+ // Query endpoints
14
17
  router.post('/find', findMetrics);
18
+ router.get('/stats', getMetricsStats);
19
+ router.get('/', findMetrics);
20
+ router.post('/', insertMetricsToDb);
15
21
  return router;
16
22
  };
17
23
  export default getMetricsRoutes;
@@ -5,7 +5,6 @@ import logger from "../utils/logger.js";
5
5
  import { pluginService } from "./pluginService.js";
6
6
  import { fileURLToPath } from "url";
7
7
  export const listPlugins = (req, res) => {
8
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
9
8
  const plugins = pluginService.getPlugins().map(({ process, ...rest }) => rest);
10
9
  res.send({
11
10
  pluginsCount: plugins.length,
@@ -56,16 +55,12 @@ export const registerPlugin = async (req, res) => {
56
55
  return;
57
56
  }
58
57
  const isCjs = typeof __filename !== "undefined" && typeof __dirname !== "undefined";
59
- const __filenameUniversal = isCjs
60
- ? __filename
61
- : fileURLToPath(import.meta.url);
62
- const __dirnameUniversal = isCjs
63
- ? __dirname
64
- : path.dirname(__filenameUniversal);
58
+ // @ts-ignore -- import.meta no existe en el build CJS
59
+ const currentDirectory = isCjs ? __dirname : path.dirname(fileURLToPath(import.meta.url));
65
60
  const pluginProcessFile = isCjs
66
61
  ? "pluginProcess.cjs"
67
62
  : "pluginProcess.js";
68
- const child = fork(path.resolve(__dirnameUniversal, pluginProcessFile), [], {
63
+ const child = fork(path.resolve(currentDirectory, pluginProcessFile), [], {
69
64
  stdio: ["pipe", "pipe", "pipe", "ipc"],
70
65
  });
71
66
  child.stdout?.on("data", (data) => {
@@ -108,7 +103,7 @@ export const registerPlugin = async (req, res) => {
108
103
  });
109
104
  };
110
105
  export const activatePlugin = (req, res) => {
111
- const { id } = req.params;
106
+ const id = req.params.id;
112
107
  const plugin = pluginService.getPlugins().find((p) => p.id === id);
113
108
  if (!plugin) {
114
109
  res.status(404).send(`Plugin with id "${id}" not found.`);
@@ -118,7 +113,7 @@ export const activatePlugin = (req, res) => {
118
113
  res.status(200).send(`Plugin "${id}" activated.`);
119
114
  };
120
115
  export const deactivatePlugin = (req, res) => {
121
- const { id } = req.params;
116
+ const id = req.params.id;
122
117
  const plugin = pluginService.getPlugins().find((p) => p.id === id);
123
118
  if (!plugin) {
124
119
  res.status(404).send(`Plugin with id "${id}" not found.`);
@@ -128,7 +123,7 @@ export const deactivatePlugin = (req, res) => {
128
123
  res.status(200).send(`Plugin "${id}" deactivated.`);
129
124
  };
130
125
  export const deletePlugin = (req, res) => {
131
- const { id } = req.params;
126
+ const id = req.params.id;
132
127
  const plugin = pluginService.getPlugins().find((p) => p.id === id);
133
128
  if (!plugin) {
134
129
  res.status(404).send(`Plugin with id "${id}" not found.`);
@@ -1,9 +1,7 @@
1
1
  import logger from "../utils/logger.js";
2
2
  class PluginService {
3
- constructor() {
4
- this.plugins = [];
5
- this.enabled = false;
6
- }
3
+ plugins = [];
4
+ enabled = false;
7
5
  getPlugins() {
8
6
  return this.plugins;
9
7
  }
@@ -1,5 +1,26 @@
1
1
  import { inMemoryDbSpanExporter } from '../telemetry/telemetryRegistry.js';
2
2
  import { convertRegexRecursively } from '../utils/regexUtils.js';
3
+ /**
4
+ * Parse import data from NDJSON or JSON format
5
+ * @param contentType - Content-Type header
6
+ * @param body - Request body (string for NDJSON, object for JSON)
7
+ * @returns Array of span objects
8
+ */
9
+ function parseImportData(body) {
10
+ if (typeof body !== 'string') {
11
+ throw new Error('Import must be NDJSON format (plain text with one JSON object per line)');
12
+ }
13
+ const lines = body.split('\n').filter((line) => line.trim());
14
+ return lines.map((line, index) => {
15
+ try {
16
+ return JSON.parse(line);
17
+ }
18
+ catch {
19
+ console.error(`Failed to parse NDJSON line ${index + 1}: ${line}`);
20
+ throw new Error(`Invalid JSON on line ${index + 1}`);
21
+ }
22
+ });
23
+ }
3
24
  export const startTraces = (req, res) => {
4
25
  inMemoryDbSpanExporter.enable();
5
26
  res.send('Traces started');
@@ -26,37 +47,45 @@ export const listTraces = async (req, res) => {
26
47
  res.status(500).send({ error: 'Failed to list traces data' });
27
48
  }
28
49
  };
29
- export const findTraces = (req, res) => {
30
- const body = req.body;
31
- const query = body?.query ? body.query : {};
50
+ export const findTraces = async (req, res) => {
51
+ const body = req.body || {};
52
+ const findQuery = body.query || {};
53
+ const limit = parseInt(body.limit) || 50;
54
+ const sortOrder = body.sort || null;
32
55
  let processedQuery;
33
56
  try {
34
- processedQuery = convertRegexRecursively(query);
57
+ processedQuery = convertRegexRecursively(findQuery);
35
58
  }
36
59
  catch (error) {
37
60
  console.error(error.message);
38
61
  res.status(400).send({ error: error.message });
39
62
  return; // Exit if invalid regex was encountered
40
63
  }
41
- inMemoryDbSpanExporter.find(processedQuery, (err, docs) => {
42
- if (err) {
43
- console.error(err);
44
- res.status(400).send({ spansCount: 0, spans: [], error: err.message });
45
- return; // Exit the function to prevent further execution
46
- }
47
- const spans = docs;
48
- res.send({ spansCount: spans.length, spans: spans });
49
- });
64
+ try {
65
+ const findConfig = {
66
+ query: processedQuery,
67
+ limit,
68
+ sortOrder
69
+ };
70
+ const docs = await inMemoryDbSpanExporter.find(findConfig);
71
+ res.send({
72
+ spansCount: docs.length,
73
+ spans: docs,
74
+ });
75
+ }
76
+ catch (err) {
77
+ console.error(err);
78
+ res.status(500).send({ spansCount: 0, spans: [], error: err.message });
79
+ }
50
80
  };
51
81
  export const insertTracesToDb = async (req, res) => {
52
- const jsonContent = req.body.spans;
82
+ const jsonContent = (req.body || {}).spans;
53
83
  const resetData = req.query.reset === 'true';
54
84
  if (!Array.isArray(jsonContent)) {
55
85
  res.status(400).send({ error: 'Invalid data format.' });
56
86
  return;
57
87
  }
58
88
  const cleanedTraces = jsonContent.map((trace) => {
59
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
60
89
  const { _id, ...rest } = trace; // Remove _id if it exists
61
90
  return rest; // Return the cleaned trace object
62
91
  });
@@ -83,8 +112,43 @@ export const insertTracesToDb = async (req, res) => {
83
112
  res.status(500).send({ error: 'Failed to reset and insert data', details: err.message });
84
113
  }
85
114
  };
115
+ export const importTraces = async (req, res) => {
116
+ const resetData = req.query.reset === 'true';
117
+ try {
118
+ // Parse NDJSON format
119
+ const spans = parseImportData(req.body);
120
+ if (spans.length === 0) {
121
+ res.status(400).send({ error: 'No valid traces found in import data' });
122
+ return;
123
+ }
124
+ const cleanedTraces = spans.map((trace) => {
125
+ const { _id, ...rest } = trace; // Remove _id if it exists
126
+ return rest;
127
+ });
128
+ let message = '';
129
+ if (resetData) {
130
+ inMemoryDbSpanExporter.reset();
131
+ message += 'Traces Database reset. ';
132
+ }
133
+ await new Promise((resolve, reject) => {
134
+ inMemoryDbSpanExporter.insert(cleanedTraces, (err, newDocs) => {
135
+ if (err) {
136
+ console.error('Error importing traces:', err);
137
+ return reject(err);
138
+ }
139
+ resolve(newDocs);
140
+ });
141
+ });
142
+ message += `Imported ${cleanedTraces.length} traces.`;
143
+ res.send({ message, ImportedTracesCount: cleanedTraces.length });
144
+ }
145
+ catch (err) {
146
+ console.error('Import failed:', err);
147
+ res.status(400).send({ error: 'Failed to import traces', details: err.message });
148
+ }
149
+ };
86
150
  export const setTraceRetentionTime = (req, res) => {
87
- const retentionTimeInSeconds = req.body.retentionTimeInSeconds;
151
+ const retentionTimeInSeconds = (req.body || {}).retentionTimeInSeconds;
88
152
  if (typeof retentionTimeInSeconds !== 'number' || retentionTimeInSeconds <= 0) {
89
153
  res.status(400).send({ error: 'Invalid retention time. Must be a positive number.' });
90
154
  return;
@@ -96,3 +160,25 @@ export const getTraceRetentionTime = (req, res) => {
96
160
  const retentionTimeInSeconds = inMemoryDbSpanExporter.retentionTimeInSeconds || 0;
97
161
  res.send({ retentionTimeInSeconds: retentionTimeInSeconds });
98
162
  };
163
+ export const exportTraces = async (req, res) => {
164
+ try {
165
+ const findConfig = {
166
+ query: {},
167
+ sortOrder: { startTime: -1 }
168
+ };
169
+ const docs = await inMemoryDbSpanExporter.find(findConfig);
170
+ const timestamp = new Date().toISOString().slice(0, 19).replace(/[-T:]/g, '');
171
+ res.setHeader('Content-Type', 'application/x-ndjson');
172
+ res.setHeader('Content-Disposition', `attachment; filename="traces-${timestamp}.ndjson"`);
173
+ res.setHeader('Transfer-Encoding', 'chunked');
174
+ // Stream as NDJSON (one JSON object per line)
175
+ docs.forEach(doc => {
176
+ res.write(JSON.stringify(doc) + '\n');
177
+ });
178
+ res.end();
179
+ }
180
+ catch (err) {
181
+ console.error('Failed to export traces:', err);
182
+ res.status(500).send({ error: 'Failed to export traces', details: err.message });
183
+ }
184
+ };
@@ -1,5 +1,5 @@
1
- import { Router } from 'express';
2
- import { startTraces, stopTraces, statusTraces, resetTraces, listTraces, findTraces, insertTracesToDb, setTraceRetentionTime, getTraceRetentionTime } from './traceController.js';
1
+ import { Router, text } from 'express';
2
+ import { startTraces, stopTraces, statusTraces, resetTraces, listTraces, findTraces, insertTracesToDb, importTraces, setTraceRetentionTime, getTraceRetentionTime, exportTraces } from './traceController.js';
3
3
  export const getTraceRoutes = () => {
4
4
  const router = Router();
5
5
  // Telemetry Control
@@ -7,6 +7,9 @@ export const getTraceRoutes = () => {
7
7
  router.post('/stop', stopTraces);
8
8
  router.get('/status', statusTraces);
9
9
  router.post('/reset', resetTraces);
10
+ router.get('/export', exportTraces);
11
+ // Use text middleware for import to handle NDJSON format
12
+ router.post('/import', text({ type: 'application/x-ndjson', limit: '500mb' }), importTraces);
10
13
  router.get('/', listTraces);
11
14
  router.post('/', insertTracesToDb);
12
15
  router.post('/find', findTraces);
@@ -11,17 +11,17 @@ export const getUIRoutes = () => {
11
11
  relativePath = '../../dist/ui';
12
12
  logger.warn('🚧 This process is serving the OASTLM UI from the build directory, but you are in development mode. For live updates, run the React app separately and access it at http://localhost:5173/.');
13
13
  }
14
- const customFilename = fileURLToPath(import.meta.url);
15
- const customDirname = path.dirname(customFilename);
16
- const staticFilesPath = path.join(customDirname, relativePath);
14
+ const isCjs = typeof __filename !== "undefined" && typeof __dirname !== "undefined";
15
+ // @ts-ignore -- import.meta no existe en el build CJS
16
+ const currentDirectory = isCjs ? __dirname : path.dirname(fileURLToPath(import.meta.url));
17
+ const staticFilesPath = path.join(currentDirectory, relativePath);
17
18
  const router = Router();
18
19
  // This only works once the app is built: src/ --> dist/esm/
19
20
  // This file: dist/esm/routes/
20
21
  // UI bundle: dist/ui/
21
22
  // For development, the UI is served separately.
22
23
  router.use(express.static(staticFilesPath));
23
- router.get('*', (_req, res) => {
24
- // Serve the index.html file for all routes
24
+ router.use((_req, res) => {
25
25
  res.sendFile(path.join(staticFilesPath, 'index.html'));
26
26
  });
27
27
  return router;
@@ -47,24 +47,18 @@ export const specLoader = (_req, res, oasTlmConfig) => {
47
47
  export const heapStats = (req, res) => {
48
48
  const heapStats = v8.getHeapStatistics();
49
49
  const roundedHeapStats = Object.getOwnPropertyNames(heapStats).reduce(function (map, stat) {
50
- //@ts-expect-error yes
51
50
  map[stat] = Math.round((heapStats[stat] / 1024 / 1024) * 1000) / 1000;
52
51
  return map;
53
52
  }, {});
54
- // @ts-expect-error yes
55
53
  roundedHeapStats['units'] = 'MB';
56
54
  res.send(roundedHeapStats);
57
55
  };
58
56
  const isCjs = typeof __filename !== "undefined" && typeof __dirname !== "undefined";
59
- const __filenameUniversal = isCjs
60
- ? __filename
61
- : fileURLToPath(import.meta.url);
62
- const __dirnameUniversal = isCjs
63
- ? __dirname
64
- : path.dirname(__filenameUniversal);
57
+ // @ts-ignore -- import.meta no existe en el build CJS
58
+ const currentDirectory = isCjs ? __dirname : path.dirname(fileURLToPath(import.meta.url));
65
59
  export const getOasTelemetrySpec = (_req, res) => {
66
60
  try {
67
- const specPath = path.join(__dirnameUniversal, '../docs/openapi.yaml');
61
+ const specPath = path.join(currentDirectory, '../docs/openapi.yaml');
68
62
  const data = readFileSync(specPath, { encoding: 'utf8', flag: 'r' });
69
63
  let json = data;
70
64
  json = JSON.stringify(yaml.load(data), null, 2);
@@ -27,8 +27,8 @@ export const getUtilsRoutes = (oasTlmConfig) => {
27
27
  res.send({ message: 'Started generating mock logs' });
28
28
  });
29
29
  // This route is NOT ignored by the spanExporter
30
- router.get('/generate-wait/:seconds?', async (req, res) => {
31
- const seconds = parseInt(req.params.seconds ?? "1", 10);
30
+ router.get('/generate-wait', async (req, res) => {
31
+ const seconds = parseInt(req.query.seconds ?? "1", 10);
32
32
  const waitTime = isNaN(seconds) ? 1 : seconds;
33
33
  await new Promise(resolve => setTimeout(resolve, waitTime * 1000));
34
34
  res.send({ waited: waitTime });
@@ -3,4 +3,8 @@ export declare const bootEnvVariables: {
3
3
  OASTLM_BOOT_MODULE_DISABLED: boolean;
4
4
  OASTLM_BOOT_LOG_LEVEL: string;
5
5
  OASTLM_BOOT_SERVICE_NAME: string;
6
+ OASTLM_BOOT_BASE_URL: string;
7
+ OASTLM_BOOT_AUTOINSTRUMENTATIONS_NODE_DISABLED: boolean;
8
+ OASTLM_BOOT_AUTOINSTRUMENTATIONS_LOGS_DISABLED: boolean;
9
+ OASTLM_BOOT_STORAGE_PATH: string;
6
10
  };