@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.
- package/.env.example +21 -2
- package/README.md +1 -2
- package/dist/cjs/config/bootConfig.cjs +19 -14
- package/dist/cjs/config/config.cjs +112 -125
- package/dist/cjs/config/config.types.cjs +1 -4
- package/dist/cjs/docs/openapi.yaml +158 -4
- package/dist/cjs/index.cjs +27 -30
- package/dist/cjs/routesManager.cjs +62 -70
- package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.cjs +202 -190
- package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.cjs +204 -99
- package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.cjs +152 -116
- package/dist/cjs/telemetry/custom-implementations/instrumentations/logsInstrumentation.cjs +92 -0
- package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/Chunk.cjs +159 -0
- package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/Series.cjs +168 -0
- package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/SeriesRegistry.cjs +389 -0
- package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/types.cjs +2 -0
- package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/utils.cjs +77 -0
- package/dist/cjs/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.cjs +65 -63
- package/dist/cjs/telemetry/custom-implementations/processors/dynamicMultiSpanProcessor.cjs +63 -62
- package/dist/cjs/telemetry/custom-implementations/utils/circular.cjs +47 -47
- package/dist/cjs/telemetry/custom-implementations/utils/storagePath.cjs +39 -0
- package/dist/cjs/telemetry/custom-implementations/wrappers.cjs +141 -138
- package/dist/cjs/telemetry/initializeTelemetry.cjs +35 -91
- package/dist/cjs/telemetry/telemetryConfigurator.cjs +70 -72
- package/dist/cjs/telemetry/telemetryRegistry.cjs +45 -31
- package/dist/cjs/tlm-ai/agent.cjs +49 -64
- package/dist/cjs/tlm-ai/aiController.cjs +54 -76
- package/dist/cjs/tlm-ai/aiRoutes.cjs +17 -20
- package/dist/cjs/tlm-ai/aiService.cjs +91 -95
- package/dist/cjs/tlm-ai/tools.cjs +177 -174
- package/dist/cjs/tlm-auth/authController.cjs +80 -123
- package/dist/cjs/tlm-auth/authMiddleware.cjs +25 -30
- package/dist/cjs/tlm-auth/authRoutes.cjs +11 -14
- package/dist/cjs/tlm-log/logController.cjs +171 -116
- package/dist/cjs/tlm-log/logRoutes.cjs +20 -20
- package/dist/cjs/tlm-metric/metricsController.cjs +211 -121
- package/dist/cjs/tlm-metric/metricsRoutes.cjs +23 -20
- package/dist/cjs/tlm-plugin/pluginController.cjs +128 -140
- package/dist/cjs/tlm-plugin/pluginProcess.cjs +89 -94
- package/dist/cjs/tlm-plugin/pluginRoutes.cjs +11 -14
- package/dist/cjs/tlm-plugin/pluginService.cjs +73 -74
- package/dist/cjs/tlm-trace/traceController.cjs +169 -117
- package/dist/cjs/tlm-trace/traceRoutes.cjs +20 -20
- package/dist/cjs/tlm-ui/uiRoutes.cjs +63 -32
- package/dist/cjs/tlm-util/utilController.cjs +68 -70
- package/dist/cjs/tlm-util/utilRoutes.cjs +51 -63
- package/dist/cjs/types/index.cjs +2 -5
- package/dist/cjs/utils/logger.cjs +38 -43
- package/dist/cjs/utils/regexUtils.cjs +22 -22
- package/dist/esm/config/bootConfig.js +6 -0
- package/dist/esm/config/config.js +1 -2
- package/dist/esm/docs/openapi.yaml +158 -4
- package/dist/esm/index.js +9 -8
- package/dist/esm/routesManager.js +6 -10
- package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.js +47 -8
- package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.js +164 -48
- package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.js +69 -29
- package/dist/esm/telemetry/custom-implementations/instrumentations/logsInstrumentation.js +85 -0
- package/dist/esm/telemetry/custom-implementations/metrics/tsdb/Chunk.js +155 -0
- package/dist/esm/telemetry/custom-implementations/metrics/tsdb/Series.js +164 -0
- package/dist/esm/telemetry/custom-implementations/metrics/tsdb/SeriesRegistry.js +382 -0
- package/dist/esm/telemetry/custom-implementations/metrics/tsdb/types.js +1 -0
- package/dist/esm/telemetry/custom-implementations/metrics/tsdb/utils.js +74 -0
- package/dist/esm/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.js +2 -1
- package/dist/esm/telemetry/custom-implementations/processors/dynamicMultiSpanProcessor.js +1 -1
- package/dist/esm/telemetry/custom-implementations/utils/storagePath.js +33 -0
- package/dist/esm/telemetry/custom-implementations/wrappers.js +5 -2
- package/dist/esm/telemetry/initializeTelemetry.js +27 -69
- package/dist/esm/telemetry/telemetryConfigurator.js +42 -40
- package/dist/esm/telemetry/telemetryRegistry.js +12 -1
- package/dist/esm/tlm-ai/agent.js +5 -3
- package/dist/esm/tlm-ai/aiController.js +3 -3
- package/dist/esm/tlm-ai/aiService.js +6 -2
- package/dist/esm/tlm-ai/tools.js +5 -9
- package/dist/esm/tlm-auth/authController.js +3 -2
- package/dist/esm/tlm-log/logController.js +84 -4
- package/dist/esm/tlm-log/logRoutes.js +5 -2
- package/dist/esm/tlm-metric/metricsController.js +172 -49
- package/dist/esm/tlm-metric/metricsRoutes.js +10 -4
- package/dist/esm/tlm-plugin/pluginController.js +6 -11
- package/dist/esm/tlm-plugin/pluginService.js +2 -4
- package/dist/esm/tlm-trace/traceController.js +102 -16
- package/dist/esm/tlm-trace/traceRoutes.js +5 -2
- package/dist/esm/tlm-ui/uiRoutes.js +5 -5
- package/dist/esm/tlm-util/utilController.js +3 -9
- package/dist/esm/tlm-util/utilRoutes.js +2 -2
- package/dist/types/config/bootConfig.d.ts +4 -0
- package/dist/types/config/config.d.ts +36 -7
- package/dist/types/config/config.types.d.ts +6 -0
- package/dist/types/index.d.ts +2 -3
- package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.d.ts +4 -1
- package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.d.ts +60 -15
- package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.d.ts +9 -4
- package/dist/types/telemetry/custom-implementations/instrumentations/logsInstrumentation.d.ts +23 -0
- package/dist/types/telemetry/custom-implementations/metrics/tsdb/Chunk.d.ts +49 -0
- package/dist/types/telemetry/custom-implementations/metrics/tsdb/Series.d.ts +67 -0
- package/dist/types/telemetry/custom-implementations/metrics/tsdb/SeriesRegistry.d.ts +69 -0
- package/dist/types/telemetry/custom-implementations/metrics/tsdb/types.d.ts +68 -0
- package/dist/types/telemetry/custom-implementations/metrics/tsdb/utils.d.ts +21 -0
- package/dist/types/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.d.ts +2 -2
- package/dist/types/telemetry/custom-implementations/utils/storagePath.d.ts +12 -0
- package/dist/types/telemetry/custom-implementations/wrappers.d.ts +1 -1
- package/dist/types/telemetry/telemetryConfigurator.d.ts +1 -1
- package/dist/types/telemetry/telemetryRegistry.d.ts +8 -0
- package/dist/types/tlm-ai/agent.d.ts +1 -1
- package/dist/types/tlm-ai/aiService.d.ts +1 -1
- package/dist/types/tlm-log/logController.d.ts +2 -0
- package/dist/types/tlm-metric/metricsController.d.ts +16 -2
- package/dist/types/tlm-trace/traceController.d.ts +3 -1
- package/dist/types/types/index.d.ts +2 -2
- package/dist/ui/assets/{ApiDocsPage-C_VVPPHa.js → ApiDocsPage-BFUrXE5F.js} +2 -2
- package/dist/ui/assets/CollapsibleCard-STA1GVQO.js +1 -0
- package/dist/ui/assets/DevToolsPage-BRSfZqO_.js +1 -0
- package/dist/ui/assets/LandingPage-DzeDy7q7.js +6 -0
- package/dist/ui/assets/LogsPage-BeiFrV2X.js +1 -0
- package/dist/ui/assets/{NotFoundPage-B3quk3P1.js → NotFoundPage-fRNOatbM.js} +1 -1
- package/dist/ui/assets/PluginCreatePage-Ch_RXsdf.js +50 -0
- package/dist/ui/assets/PluginPage-Cl65ZZ_n.js +27 -0
- package/dist/ui/assets/TraceSpansPage-BoK4M5Hh.js +6 -0
- package/dist/ui/assets/VirtualizedListPanel-zcj0v7DL.js +16 -0
- package/dist/ui/assets/alert-BkNVKxJN.js +1133 -0
- package/dist/ui/assets/badge-CN7FeufU.js +1 -0
- package/dist/ui/assets/{chevron-down-CPsvsmqj.js → chevron-down-CG--ounh.js} +1 -1
- package/dist/ui/assets/{chevron-up-Df9jMo1X.js → chevron-up-B6tzMAOm.js} +1 -1
- package/dist/ui/assets/{circle-alert-DOPQPvU8.js → circle-alert-BDF8Tq9y.js} +1 -1
- package/dist/ui/assets/dialog-BrpWNk36.js +15 -0
- package/dist/ui/assets/index-6xOVKwKn.js +305 -0
- package/dist/ui/assets/index-D6f1KjWV.css +1 -0
- package/dist/ui/assets/index-D96rVSkR.js +1 -0
- package/dist/ui/assets/info-99kuqpbx.js +6 -0
- package/dist/ui/assets/{input-Dzvg_ZEZ.js → input-B-01QDg_.js} +1 -1
- package/dist/ui/assets/label-CQLeZjM1.js +1 -0
- package/dist/ui/assets/{loader-circle-CrvlRy5o.js → loader-circle-BoDGk-BO.js} +1 -1
- package/dist/ui/assets/{loginPage-qa4V-B70.js → loginPage-8F4EEd1B.js} +1 -1
- package/dist/ui/assets/metrics-page-D1GxaB_c.css +1 -0
- package/dist/ui/assets/metrics-page-DPtteXqY.js +31 -0
- package/dist/ui/assets/popover-DS_8DYYt.js +11 -0
- package/dist/ui/assets/select-DYjegiXi.js +6 -0
- package/dist/ui/assets/separator-DGsRxIrl.js +6 -0
- package/dist/ui/assets/severityOptions-DEOvJqC9.js +11 -0
- package/dist/ui/assets/square-pen-DPhgYz6O.js +6 -0
- package/dist/ui/assets/switch-Di9NJH2A.js +1 -0
- package/dist/ui/assets/trace-DJq1miYa.js +1 -0
- package/dist/ui/assets/upload-BiLTpCnX.js +11 -0
- package/dist/ui/assets/{utilService-DNyqzwj0.js → utilService-CNZOmadC.js} +1 -1
- package/dist/ui/assets/wand-sparkles-CPoBNFFg.js +6 -0
- package/dist/ui/index.html +2 -2
- package/package.json +44 -48
- package/dist/ui/assets/CollapsibleCard-B3KR_8mL.js +0 -1
- package/dist/ui/assets/DevToolsPage-OyZcDcmw.js +0 -1
- package/dist/ui/assets/LandingPage-CppFBA6K.js +0 -6
- package/dist/ui/assets/LogsPage-9Fq8GArS.js +0 -26
- package/dist/ui/assets/PluginCreatePage-X_aCH4t4.js +0 -50
- package/dist/ui/assets/PluginPage-DMDSihrZ.js +0 -27
- package/dist/ui/assets/alert-jQ9HCPIf.js +0 -1133
- package/dist/ui/assets/badge-CNq0-mH5.js +0 -1
- package/dist/ui/assets/card-DFAwwhN3.js +0 -1
- package/dist/ui/assets/index-BkD6DijD.js +0 -15
- package/dist/ui/assets/index-CERGVYZK.js +0 -292
- package/dist/ui/assets/index-CSIPf9qw.css +0 -1
- package/dist/ui/assets/label-DuVnkZ4q.js +0 -1
- package/dist/ui/assets/select-DhS8YUtJ.js +0 -1
- package/dist/ui/assets/separator-isK4chBP.js +0 -6
- package/dist/ui/assets/severityOptions-O38dSOfk.js +0 -11
- package/dist/ui/assets/switch-Z3mImG9n.js +0 -1
- package/dist/ui/assets/tabs-_77MUUQe.js +0 -16
- package/dist/ui/assets/upload-C1LT4Gkb.js +0 -16
|
@@ -1,70 +1,64 @@
|
|
|
1
1
|
import { inMemoryDbMetricExporter } from '../telemetry/telemetryRegistry.js';
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
6
|
-
res.send(
|
|
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
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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 {
|
|
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
|
-
|
|
13
|
-
router.
|
|
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
|
-
|
|
60
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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,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
|
|
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(
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
const
|
|
48
|
-
res.send({
|
|
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
|
|
15
|
-
|
|
16
|
-
const
|
|
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.
|
|
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
|
-
|
|
60
|
-
|
|
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(
|
|
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
|
|
31
|
-
const seconds = parseInt(req.
|
|
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
|
};
|