@oas-tools/oas-telemetry 0.7.1 → 0.8.0-alpha.1
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 +17 -3
- package/README.md +1 -2
- package/dist/cjs/config/bootConfig.cjs +16 -14
- package/dist/cjs/config/config.cjs +120 -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/DiskLogExporter.cjs +121 -0
- package/dist/cjs/telemetry/custom-implementations/exporters/DiskMetricExporter.cjs +101 -0
- package/dist/cjs/telemetry/custom-implementations/exporters/DiskTraceExporter.cjs +103 -0
- package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.cjs +194 -190
- package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.cjs +147 -99
- package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.cjs +143 -116
- package/dist/cjs/telemetry/custom-implementations/exporters/MultiMetricExporter.cjs +57 -0
- 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 +392 -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/wrappers.cjs +209 -138
- package/dist/cjs/telemetry/initializeTelemetry.cjs +35 -91
- package/dist/cjs/telemetry/persistence/DiskImporter.cjs +85 -0
- package/dist/cjs/telemetry/persistence/DiskUtils.cjs +61 -0
- package/dist/cjs/telemetry/persistence/DiskWriter.cjs +66 -0
- package/dist/cjs/telemetry/telemetryConfigurator.cjs +139 -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 +135 -116
- package/dist/cjs/tlm-log/logRoutes.cjs +19 -20
- package/dist/cjs/tlm-log/logService.cjs +29 -0
- package/dist/cjs/tlm-metric/metricsController.cjs +154 -122
- package/dist/cjs/tlm-metric/metricsRoutes.cjs +22 -20
- package/dist/cjs/tlm-metric/metricsService.cjs +26 -0
- 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 +140 -123
- package/dist/cjs/tlm-trace/traceRoutes.cjs +19 -20
- package/dist/cjs/tlm-trace/traceService.cjs +29 -0
- 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 +5 -2
- package/dist/esm/config/config.js +9 -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/DiskLogExporter.js +114 -0
- package/dist/esm/telemetry/custom-implementations/exporters/DiskMetricExporter.js +94 -0
- package/dist/esm/telemetry/custom-implementations/exporters/DiskTraceExporter.js +96 -0
- package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.js +38 -7
- package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.js +107 -48
- package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.js +60 -29
- package/dist/esm/telemetry/custom-implementations/exporters/MultiMetricExporter.js +53 -0
- 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 +385 -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/wrappers.js +77 -6
- package/dist/esm/telemetry/initializeTelemetry.js +27 -69
- package/dist/esm/telemetry/persistence/DiskImporter.js +78 -0
- package/dist/esm/telemetry/persistence/DiskUtils.js +51 -0
- package/dist/esm/telemetry/persistence/DiskWriter.js +59 -0
- package/dist/esm/telemetry/telemetryConfigurator.js +110 -39
- 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 +62 -18
- package/dist/esm/tlm-log/logRoutes.js +3 -1
- package/dist/esm/tlm-log/logService.js +25 -0
- package/dist/esm/tlm-metric/metricsController.js +116 -50
- package/dist/esm/tlm-metric/metricsRoutes.js +8 -3
- package/dist/esm/tlm-metric/metricsService.js +22 -0
- 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 +87 -36
- package/dist/esm/tlm-trace/traceRoutes.js +3 -1
- package/dist/esm/tlm-trace/traceService.js +25 -0
- 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 +3 -0
- package/dist/types/config/config.d.ts +48 -7
- package/dist/types/config/config.types.d.ts +7 -0
- package/dist/types/index.d.ts +2 -3
- package/dist/types/telemetry/custom-implementations/exporters/DiskLogExporter.d.ts +24 -0
- package/dist/types/telemetry/custom-implementations/exporters/DiskMetricExporter.d.ts +23 -0
- package/dist/types/telemetry/custom-implementations/exporters/DiskTraceExporter.d.ts +23 -0
- package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.d.ts +3 -1
- package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.d.ts +56 -15
- package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.d.ts +8 -4
- package/dist/types/telemetry/custom-implementations/exporters/MultiMetricExporter.d.ts +9 -0
- 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/wrappers.d.ts +2 -1
- package/dist/types/telemetry/persistence/DiskImporter.d.ts +17 -0
- package/dist/types/telemetry/persistence/DiskUtils.d.ts +11 -0
- package/dist/types/telemetry/persistence/DiskWriter.d.ts +21 -0
- 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-log/logService.d.ts +4 -0
- package/dist/types/tlm-metric/metricsController.d.ts +11 -2
- package/dist/types/tlm-metric/metricsService.d.ts +6 -0
- package/dist/types/tlm-trace/traceController.d.ts +9 -7
- package/dist/types/tlm-trace/traceService.d.ts +4 -0
- package/dist/types/types/index.d.ts +2 -2
- package/dist/ui/assets/{ApiDocsPage-C_VVPPHa.js → ApiDocsPage-DTCgVbW2.js} +2 -2
- package/dist/ui/assets/CollapsibleCard-lWgfsaAn.js +1 -0
- package/dist/ui/assets/DevToolsPage-DEhf8CBy.js +1 -0
- package/dist/ui/assets/LandingPage-CfEHCDxY.js +6 -0
- package/dist/ui/assets/LogsPage-DFDKRuGH.js +1 -0
- package/dist/ui/assets/{NotFoundPage-B3quk3P1.js → NotFoundPage-DCy0DcV7.js} +1 -1
- package/dist/ui/assets/PluginCreatePage-BawZ5_-h.js +50 -0
- package/dist/ui/assets/PluginPage-D3FmgU7d.js +27 -0
- package/dist/ui/assets/TraceSpansPage-D0_L45Rb.js +6 -0
- package/dist/ui/assets/VirtualizedListPanel-q605n9He.js +16 -0
- package/dist/ui/assets/alert-DBAFshSi.js +1133 -0
- package/dist/ui/assets/badge-DGNBtnxU.js +1 -0
- package/dist/ui/assets/{chevron-down-CPsvsmqj.js → chevron-down-CFEqYzGC.js} +1 -1
- package/dist/ui/assets/{chevron-up-Df9jMo1X.js → chevron-up-lDnFwAJq.js} +1 -1
- package/dist/ui/assets/{circle-alert-DOPQPvU8.js → circle-alert-BpYUuRs7.js} +1 -1
- package/dist/ui/assets/dialog-1dRyI6SC.js +15 -0
- package/dist/ui/assets/index-C7RfU6hR.js +1 -0
- package/dist/ui/assets/index-C9dDYIpd.js +305 -0
- package/dist/ui/assets/index-D6f1KjWV.css +1 -0
- package/dist/ui/assets/info-CuJQWoBU.js +6 -0
- package/dist/ui/assets/{input-Dzvg_ZEZ.js → input-BLXaar0X.js} +1 -1
- package/dist/ui/assets/label-DfAcltsl.js +1 -0
- package/dist/ui/assets/{loader-circle-CrvlRy5o.js → loader-circle-B7oLyPsi.js} +1 -1
- package/dist/ui/assets/{loginPage-qa4V-B70.js → loginPage-DswZvOJ-.js} +1 -1
- package/dist/ui/assets/metrics-page-BhtXrfUW.js +31 -0
- package/dist/ui/assets/metrics-page-D1GxaB_c.css +1 -0
- package/dist/ui/assets/popover-IDker85U.js +11 -0
- package/dist/ui/assets/select-B8y5IidE.js +6 -0
- package/dist/ui/assets/separator-B6EzrxYY.js +6 -0
- package/dist/ui/assets/severityOptions-DtCsaAZK.js +11 -0
- package/dist/ui/assets/square-pen-D_oecB1x.js +6 -0
- package/dist/ui/assets/switch-Dqo0XkRD.js +1 -0
- package/dist/ui/assets/trace-DJq1miYa.js +1 -0
- package/dist/ui/assets/upload-prIohEdY.js +11 -0
- package/dist/ui/assets/{utilService-DNyqzwj0.js → utilService-C8TJKLqs.js} +1 -1
- package/dist/ui/assets/wand-sparkles-OgXuzsSx.js +6 -0
- package/dist/ui/index.html +2 -2
- package/package.json +44 -49
- 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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert raw format to OpenTelemetry format
|
|
3
|
+
*
|
|
4
|
+
* Raw format structure (same for GET response and INSERT input):
|
|
5
|
+
* {
|
|
6
|
+
* scope: { name, version },
|
|
7
|
+
* descriptor: { unit, description, ... },
|
|
8
|
+
* series: [{ attributes, startTimes[], endTimes[], values[] }]
|
|
9
|
+
* }
|
|
10
|
+
*
|
|
11
|
+
* OTEL format structure:
|
|
12
|
+
* {
|
|
13
|
+
* scope: { name, version },
|
|
14
|
+
* metrics: [{ descriptor, dataPoints: [{ attributes, startTime, endTime, value }] }]
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* Groups dataPoints by endTime to reconstruct original exports
|
|
18
|
+
*/
|
|
19
|
+
export function rawToOtel(rawScopeMetrics) {
|
|
20
|
+
const groupedByScope = new Map();
|
|
21
|
+
for (const result of rawScopeMetrics) {
|
|
22
|
+
// Validate that result matches expected raw format
|
|
23
|
+
if (!result.scope) {
|
|
24
|
+
throw new Error(`Invalid metric format: Missing 'scope' property. ` +
|
|
25
|
+
`Expected format: {scope: {name, version}, descriptor: {name, ...}, series: [{attributes, startTimes, endTimes, values}]}. ` +
|
|
26
|
+
`Got: ${JSON.stringify(result).substring(0, 200)}`);
|
|
27
|
+
}
|
|
28
|
+
if (!result.descriptor) {
|
|
29
|
+
throw new Error(`Invalid metric format: Missing 'descriptor' property in metric. ` +
|
|
30
|
+
`Expected format: {scope: {name, version}, descriptor: {name, ...}, series: [{attributes, startTimes, endTimes, values}]}`);
|
|
31
|
+
}
|
|
32
|
+
if (!Array.isArray(result.series)) {
|
|
33
|
+
throw new Error(`Invalid metric format: 'series' must be an array. ` +
|
|
34
|
+
`Expected format: {scope: {name, version}, descriptor: {name, ...}, series: [{attributes, startTimes, endTimes, values}]}`);
|
|
35
|
+
}
|
|
36
|
+
const scopeId = result.scope.version
|
|
37
|
+
? `${result.scope.name}@${result.scope.version}`
|
|
38
|
+
: `${result.scope.name}@none`;
|
|
39
|
+
if (!groupedByScope.has(scopeId)) {
|
|
40
|
+
groupedByScope.set(scopeId, {
|
|
41
|
+
scope: {
|
|
42
|
+
name: result.scope.name,
|
|
43
|
+
version: result.scope.version || ''
|
|
44
|
+
},
|
|
45
|
+
metrics: []
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const scopeData = groupedByScope.get(scopeId);
|
|
49
|
+
const dataPoints = [];
|
|
50
|
+
for (const series of result.series) {
|
|
51
|
+
for (let i = 0; i < series.endTimes.length; i++) {
|
|
52
|
+
const startTimeNs = series.startTimes?.[i];
|
|
53
|
+
const endTimeNs = series.endTimes[i];
|
|
54
|
+
if (!startTimeNs)
|
|
55
|
+
continue;
|
|
56
|
+
const startSec = Math.floor(startTimeNs / 1_000_000_000);
|
|
57
|
+
const startNano = startTimeNs % 1_000_000_000;
|
|
58
|
+
const endSec = Math.floor(endTimeNs / 1_000_000_000);
|
|
59
|
+
const endNano = endTimeNs % 1_000_000_000;
|
|
60
|
+
dataPoints.push({
|
|
61
|
+
attributes: series.attributes,
|
|
62
|
+
startTime: [startSec, startNano],
|
|
63
|
+
endTime: [endSec, endNano],
|
|
64
|
+
value: series.values[i]
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
scopeData.metrics.push({
|
|
69
|
+
descriptor: result.descriptor,
|
|
70
|
+
dataPoints
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return Array.from(groupedByScope.values());
|
|
74
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { callWithTimeout } from '@opentelemetry/core';
|
|
2
2
|
import logger from '../../../utils/logger.js'; // optional if you want logging
|
|
3
3
|
export class DynamicMultiLogRecordProcessor {
|
|
4
|
+
_processors = [];
|
|
5
|
+
_forceFlushTimeoutMillis;
|
|
4
6
|
constructor(initialProcessors = [], forceFlushTimeoutMillis = 30000) {
|
|
5
|
-
this._processors = [];
|
|
6
7
|
this._processors = [...initialProcessors];
|
|
7
8
|
this._forceFlushTimeoutMillis = forceFlushTimeoutMillis;
|
|
8
9
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { ExportResultCode } from "@opentelemetry/core";
|
|
1
2
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
2
3
|
import logger from "../../utils/logger.js";
|
|
3
4
|
export class Enabler {
|
|
5
|
+
_enabled = true;
|
|
4
6
|
constructor(enabled) {
|
|
5
|
-
this._enabled = true;
|
|
6
7
|
if (typeof enabled === 'boolean') {
|
|
7
8
|
this._enabled = enabled;
|
|
8
9
|
}
|
|
@@ -40,6 +41,7 @@ export class Enabler {
|
|
|
40
41
|
*
|
|
41
42
|
*/
|
|
42
43
|
export class EnablerSpanExporter extends Enabler {
|
|
44
|
+
exporter;
|
|
43
45
|
constructor(exporter) {
|
|
44
46
|
super();
|
|
45
47
|
this.exporter = exporter;
|
|
@@ -60,6 +62,7 @@ export class EnablerSpanExporter extends Enabler {
|
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
export class EnablerLogExporter extends Enabler {
|
|
65
|
+
exporter;
|
|
63
66
|
constructor(exporter) {
|
|
64
67
|
super();
|
|
65
68
|
this.exporter = exporter;
|
|
@@ -74,9 +77,9 @@ export class EnablerLogExporter extends Enabler {
|
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
class EnablerMultiExporter extends Enabler {
|
|
80
|
+
_exporters = [];
|
|
77
81
|
constructor(exporters) {
|
|
78
82
|
super();
|
|
79
|
-
this._exporters = [];
|
|
80
83
|
if (exporters && Array.isArray(exporters)) {
|
|
81
84
|
this._exporters = exporters;
|
|
82
85
|
}
|
|
@@ -92,13 +95,48 @@ class EnablerMultiExporter extends Enabler {
|
|
|
92
95
|
this._exporters.push(exporter);
|
|
93
96
|
}
|
|
94
97
|
}
|
|
98
|
+
clearExporters() {
|
|
99
|
+
this._exporters = [];
|
|
100
|
+
}
|
|
95
101
|
}
|
|
96
102
|
export class EnablerMultiSpanExporter extends EnablerMultiExporter {
|
|
97
103
|
export(spans, resultCallback) {
|
|
98
104
|
logger.debug(`EnablerMultiSpanExporter.export called with ${spans.length} spans, exporters: ${this._exporters?.length ?? 0}, enabled: ${this.isEnabled()}`);
|
|
99
|
-
if (this.isEnabled()
|
|
100
|
-
|
|
105
|
+
if (!this.isEnabled() || !this._exporters || this._exporters.length === 0) {
|
|
106
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
107
|
+
return;
|
|
101
108
|
}
|
|
109
|
+
let pending = this._exporters.length;
|
|
110
|
+
let failed = false;
|
|
111
|
+
let firstError;
|
|
112
|
+
this._exporters.forEach((exporter) => {
|
|
113
|
+
try {
|
|
114
|
+
exporter.export(spans, (result) => {
|
|
115
|
+
if (result.code === ExportResultCode.FAILED) {
|
|
116
|
+
failed = true;
|
|
117
|
+
if (result.error && !firstError) {
|
|
118
|
+
firstError = result.error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
pending -= 1;
|
|
122
|
+
if (pending === 0) {
|
|
123
|
+
resultCallback(failed
|
|
124
|
+
? { code: ExportResultCode.FAILED, error: firstError }
|
|
125
|
+
: { code: ExportResultCode.SUCCESS });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
failed = true;
|
|
131
|
+
if (!firstError) {
|
|
132
|
+
firstError = error instanceof Error ? error : new Error(String(error));
|
|
133
|
+
}
|
|
134
|
+
pending -= 1;
|
|
135
|
+
if (pending === 0) {
|
|
136
|
+
resultCallback({ code: ExportResultCode.FAILED, error: firstError });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
102
140
|
}
|
|
103
141
|
async shutdown() {
|
|
104
142
|
if (this._exporters) {
|
|
@@ -109,9 +147,41 @@ export class EnablerMultiSpanExporter extends EnablerMultiExporter {
|
|
|
109
147
|
}
|
|
110
148
|
export class EnablerMultiLogExporter extends EnablerMultiExporter {
|
|
111
149
|
export(logs, resultCallback) {
|
|
112
|
-
if (this.isEnabled()
|
|
113
|
-
|
|
150
|
+
if (!this.isEnabled() || !this._exporters || this._exporters.length === 0) {
|
|
151
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
152
|
+
return;
|
|
114
153
|
}
|
|
154
|
+
let pending = this._exporters.length;
|
|
155
|
+
let failed = false;
|
|
156
|
+
let firstError;
|
|
157
|
+
this._exporters.forEach((exporter) => {
|
|
158
|
+
try {
|
|
159
|
+
exporter.export(logs, (result) => {
|
|
160
|
+
if (result.code === ExportResultCode.FAILED) {
|
|
161
|
+
failed = true;
|
|
162
|
+
if (result.error && !firstError) {
|
|
163
|
+
firstError = result.error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
pending -= 1;
|
|
167
|
+
if (pending === 0) {
|
|
168
|
+
resultCallback(failed
|
|
169
|
+
? { code: ExportResultCode.FAILED, error: firstError }
|
|
170
|
+
: { code: ExportResultCode.SUCCESS });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
failed = true;
|
|
176
|
+
if (!firstError) {
|
|
177
|
+
firstError = error instanceof Error ? error : new Error(String(error));
|
|
178
|
+
}
|
|
179
|
+
pending -= 1;
|
|
180
|
+
if (pending === 0) {
|
|
181
|
+
resultCallback({ code: ExportResultCode.FAILED, error: firstError });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
115
185
|
}
|
|
116
186
|
async shutdown() {
|
|
117
187
|
if (this._exporters) {
|
|
@@ -121,6 +191,7 @@ export class EnablerMultiLogExporter extends EnablerMultiExporter {
|
|
|
121
191
|
}
|
|
122
192
|
}
|
|
123
193
|
export class EnablerMetricReader extends Enabler {
|
|
194
|
+
reader;
|
|
124
195
|
constructor(reader) {
|
|
125
196
|
super();
|
|
126
197
|
this.reader = reader;
|
|
@@ -1,79 +1,37 @@
|
|
|
1
|
-
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
|
|
2
1
|
import logger from '../utils/logger.js';
|
|
3
|
-
import { registerInstrumentations } from '@opentelemetry/instrumentation';
|
|
4
|
-
import { dynamicMultiLogProcessor, dynamicMultiSpanProcessor, oasTelemetryResource, originalConsoleMethods } from './telemetryRegistry.js';
|
|
5
|
-
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
6
|
-
import { SeverityNumber } from '@opentelemetry/api-logs';
|
|
7
|
-
import { LoggerProvider } from '@opentelemetry/sdk-logs';
|
|
8
2
|
import { bootEnvVariables } from '../config/bootConfig.js';
|
|
9
|
-
import
|
|
3
|
+
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
|
|
4
|
+
import { instrumentations, isBootInitialized, setBootInitialized } from './telemetryRegistry.js';
|
|
5
|
+
import { LogsInstrumentation } from './custom-implementations/instrumentations/logsInstrumentation.js';
|
|
6
|
+
import { RuntimeNodeInstrumentation } from '@opentelemetry/instrumentation-runtime-node';
|
|
10
7
|
// THIS INSTRUMENTATIONS NEED TO BE LOADED BEFORE ANYTHING ELSE
|
|
11
8
|
// They use monkey-patching to instrument the HTTP server and client.
|
|
12
9
|
// THIS FILE MUST BE CALLED BEFORE ANYTHING ELSE
|
|
13
10
|
if (bootEnvVariables.OASTLM_BOOT_MODULE_DISABLED) {
|
|
14
|
-
logger.info('🚫 OASTLM module is disabled,
|
|
11
|
+
logger.info('🚫 OASTLM module is disabled, Auto Instrumentations not initialized.');
|
|
15
12
|
}
|
|
16
13
|
else {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const loggerProvider = new LoggerProvider({
|
|
40
|
-
resource: oasTelemetryResource,
|
|
41
|
-
processors: [dynamicMultiLogProcessor]
|
|
42
|
-
});
|
|
43
|
-
// Get a logger instance
|
|
44
|
-
const loggerInstance = loggerProvider.getLogger('oas-telemetry'); // Use loggerProvider to get the logger
|
|
45
|
-
Object.keys(originalConsoleMethods).forEach((method) => {
|
|
46
|
-
// @ts-expect-error yes
|
|
47
|
-
console[method] = (...args) => {
|
|
48
|
-
const severity = getSeverityForMethod(method);
|
|
49
|
-
loggerInstance.emit({
|
|
50
|
-
severityNumber: severity.number,
|
|
51
|
-
severityText: severity.text,
|
|
52
|
-
body: util.format(...args),
|
|
53
|
-
attributes: { 'source': `console.${method}`, "library": "oas-telemetry" },
|
|
54
|
-
});
|
|
55
|
-
// @ts-expect-error yes
|
|
56
|
-
originalConsoleMethods[method](...args);
|
|
57
|
-
};
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
function initializeMetrics() {
|
|
61
|
-
logger.info('📈 Initializing MeterProvider');
|
|
62
|
-
// WARN: Default PeriodicExportingMetricReader is added post initialization (see telemetryConfigurator.ts)
|
|
63
|
-
// The in memory exporter is added by default to that reader. More readers are allowed to be added dynamically
|
|
64
|
-
}
|
|
65
|
-
function getSeverityForMethod(method) {
|
|
66
|
-
switch (method) {
|
|
67
|
-
case "log":
|
|
68
|
-
case "info":
|
|
69
|
-
return { number: SeverityNumber.INFO, text: "INFO" };
|
|
70
|
-
case "debug":
|
|
71
|
-
return { number: SeverityNumber.DEBUG, text: "DEBUG" };
|
|
72
|
-
case "warn":
|
|
73
|
-
return { number: SeverityNumber.WARN, text: "WARN" };
|
|
74
|
-
case "error":
|
|
75
|
-
return { number: SeverityNumber.ERROR, text: "ERROR" };
|
|
76
|
-
default:
|
|
77
|
-
return { number: SeverityNumber.INFO, text: "INFO" };
|
|
14
|
+
if (!isBootInitialized()) {
|
|
15
|
+
// Only HTTP instrumentation por ahora
|
|
16
|
+
const httpInstrumentation = new HttpInstrumentation({
|
|
17
|
+
// Ignore internal telemetry routes
|
|
18
|
+
ignoreIncomingRequestHook: (req) => {
|
|
19
|
+
const url = req.url || '';
|
|
20
|
+
const path = url.split('?')[0]; // Remove query params
|
|
21
|
+
// Ignore all baseUrl routes except those with 'generate'
|
|
22
|
+
if (path.includes(bootEnvVariables.OASTLM_BOOT_BASE_URL)) {
|
|
23
|
+
return !path.includes('generate');
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
if (!bootEnvVariables.OASTLM_BOOT_AUTOINSTRUMENTATIONS_NODE_DISABLED) {
|
|
29
|
+
instrumentations.push(httpInstrumentation, new RuntimeNodeInstrumentation());
|
|
30
|
+
}
|
|
31
|
+
if (!bootEnvVariables.OASTLM_BOOT_AUTOINSTRUMENTATIONS_LOGS_DISABLED) {
|
|
32
|
+
instrumentations.push(new LogsInstrumentation());
|
|
33
|
+
}
|
|
34
|
+
setBootInitialized(true);
|
|
35
|
+
logger.info('✅ Auto Instrumentations created successfully');
|
|
78
36
|
}
|
|
79
37
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import logger from '../../utils/logger.js';
|
|
3
|
+
import { decodeRecordBatchFrame, listSegments, DEFAULT_SEGMENT_PREFIX } from './DiskUtils.js';
|
|
4
|
+
export class DiskImporter {
|
|
5
|
+
directoryPath;
|
|
6
|
+
segmentPrefix;
|
|
7
|
+
importBatchSize;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.directoryPath = options.directoryPath;
|
|
10
|
+
this.segmentPrefix = options.segmentPrefix || DEFAULT_SEGMENT_PREFIX;
|
|
11
|
+
this.importBatchSize = options.importBatchSize || 500;
|
|
12
|
+
}
|
|
13
|
+
async import(onBatch) {
|
|
14
|
+
const stats = {
|
|
15
|
+
segmentFilesRead: 0,
|
|
16
|
+
importedRecords: 0,
|
|
17
|
+
failedFrames: 0,
|
|
18
|
+
};
|
|
19
|
+
const segments = await listSegments(this.directoryPath, this.segmentPrefix);
|
|
20
|
+
if (!segments.length) {
|
|
21
|
+
return stats;
|
|
22
|
+
}
|
|
23
|
+
const importBuffer = [];
|
|
24
|
+
const flushImportBuffer = async () => {
|
|
25
|
+
if (!importBuffer.length)
|
|
26
|
+
return;
|
|
27
|
+
const batch = importBuffer.splice(0, importBuffer.length);
|
|
28
|
+
await onBatch(batch);
|
|
29
|
+
stats.importedRecords += batch.length;
|
|
30
|
+
};
|
|
31
|
+
for (const segment of segments) {
|
|
32
|
+
stats.segmentFilesRead += 1;
|
|
33
|
+
let fileBuffer;
|
|
34
|
+
try {
|
|
35
|
+
fileBuffer = await fs.readFile(segment.filePath);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
logger.warn(`[DiskImporter] Could not read segment ${segment.filePath}: ${error?.message || error}`);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
let offset = 0;
|
|
42
|
+
while (offset + 4 <= fileBuffer.length) {
|
|
43
|
+
const frameLength = fileBuffer.readUInt32LE(offset);
|
|
44
|
+
offset += 4;
|
|
45
|
+
if (frameLength <= 0) {
|
|
46
|
+
stats.failedFrames += 1;
|
|
47
|
+
logger.warn(`[DiskImporter] Invalid frame length in ${segment.filePath}; stopping segment parse.`);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
if (offset + frameLength > fileBuffer.length) {
|
|
51
|
+
stats.failedFrames += 1;
|
|
52
|
+
logger.warn(`[DiskImporter] Truncated frame in ${segment.filePath}; stopping segment parse.`);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
const framePayload = fileBuffer.subarray(offset, offset + frameLength);
|
|
56
|
+
offset += frameLength;
|
|
57
|
+
try {
|
|
58
|
+
const decodedSpans = decodeRecordBatchFrame(framePayload);
|
|
59
|
+
importBuffer.push(...decodedSpans);
|
|
60
|
+
while (importBuffer.length >= this.importBatchSize) {
|
|
61
|
+
const batch = importBuffer.splice(0, this.importBatchSize);
|
|
62
|
+
await onBatch(batch);
|
|
63
|
+
stats.importedRecords += batch.length;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
stats.failedFrames += 1;
|
|
68
|
+
logger.warn(`[DiskImporter] Failed to decode frame from ${segment.filePath}: ${error?.message || error}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (offset !== fileBuffer.length) {
|
|
72
|
+
logger.warn(`[DiskImporter] Segment ${segment.filePath} has trailing or incomplete bytes.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
await flushImportBuffer();
|
|
76
|
+
return stats;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { gzipSync, gunzipSync } from 'zlib';
|
|
4
|
+
export const DEFAULT_SEGMENT_PREFIX = 'traces';
|
|
5
|
+
export const SEGMENT_EXT = '.oastlm';
|
|
6
|
+
export const DEFAULT_MAX_SEGMENT_BYTES = 8 * 1024 * 1024;
|
|
7
|
+
export function getSegmentName(index, prefix = DEFAULT_SEGMENT_PREFIX) {
|
|
8
|
+
return `${prefix}-${index.toString().padStart(6, '0')}${SEGMENT_EXT}`;
|
|
9
|
+
}
|
|
10
|
+
export async function listSegments(directoryPath, prefix = DEFAULT_SEGMENT_PREFIX) {
|
|
11
|
+
try {
|
|
12
|
+
const files = await fs.readdir(directoryPath);
|
|
13
|
+
const regex = new RegExp(`^${prefix}-(\\d{6})${SEGMENT_EXT.replace('.', '\\.')}$`);
|
|
14
|
+
return files
|
|
15
|
+
.map((name) => {
|
|
16
|
+
const match = name.match(regex);
|
|
17
|
+
if (!match)
|
|
18
|
+
return null;
|
|
19
|
+
const index = Number.parseInt(match[1], 10);
|
|
20
|
+
return { index, filePath: path.join(directoryPath, name) };
|
|
21
|
+
})
|
|
22
|
+
.filter((value) => value !== null)
|
|
23
|
+
.sort((a, b) => a.index - b.index);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error?.code === 'ENOENT') {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function encodeRecordBatchFrame(records) {
|
|
33
|
+
const lineDelimitedJson = records.map((record) => JSON.stringify(record)).join('\n') + '\n';
|
|
34
|
+
const compressed = gzipSync(Buffer.from(lineDelimitedJson, 'utf-8'));
|
|
35
|
+
const frame = Buffer.allocUnsafe(4 + compressed.length);
|
|
36
|
+
frame.writeUInt32LE(compressed.length, 0);
|
|
37
|
+
compressed.copy(frame, 4);
|
|
38
|
+
return frame;
|
|
39
|
+
}
|
|
40
|
+
export function decodeRecordBatchFrame(framePayload) {
|
|
41
|
+
const decompressed = gunzipSync(framePayload).toString('utf-8');
|
|
42
|
+
const lines = decompressed.split('\n').filter((line) => line.trim().length > 0);
|
|
43
|
+
return lines.map((line, index) => {
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(line);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new Error(`Invalid JSON line at index ${index}`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { encodeRecordBatchFrame, getSegmentName, listSegments, DEFAULT_MAX_SEGMENT_BYTES, DEFAULT_SEGMENT_PREFIX, } from './DiskUtils.js';
|
|
4
|
+
export class DiskWriter {
|
|
5
|
+
directoryPath;
|
|
6
|
+
segmentPrefix;
|
|
7
|
+
maxSegmentBytes;
|
|
8
|
+
initialized = false;
|
|
9
|
+
currentSegmentIndex = 0;
|
|
10
|
+
currentSegmentPath = '';
|
|
11
|
+
currentSegmentSize = 0;
|
|
12
|
+
writeQueue = Promise.resolve();
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.directoryPath = path.resolve(options.directoryPath);
|
|
15
|
+
this.segmentPrefix = options.segmentPrefix || DEFAULT_SEGMENT_PREFIX;
|
|
16
|
+
this.maxSegmentBytes = options.maxSegmentBytes || DEFAULT_MAX_SEGMENT_BYTES;
|
|
17
|
+
}
|
|
18
|
+
async appendRecords(records) {
|
|
19
|
+
if (!records.length)
|
|
20
|
+
return;
|
|
21
|
+
const frame = encodeRecordBatchFrame(records);
|
|
22
|
+
this.writeQueue = this.writeQueue.then(() => this.appendFrame(frame));
|
|
23
|
+
return this.writeQueue;
|
|
24
|
+
}
|
|
25
|
+
async flush() {
|
|
26
|
+
await this.writeQueue;
|
|
27
|
+
}
|
|
28
|
+
async ensureInitialized() {
|
|
29
|
+
if (this.initialized)
|
|
30
|
+
return;
|
|
31
|
+
await fs.mkdir(this.directoryPath, { recursive: true });
|
|
32
|
+
const segments = await listSegments(this.directoryPath, this.segmentPrefix);
|
|
33
|
+
if (segments.length === 0) {
|
|
34
|
+
this.currentSegmentIndex = 0;
|
|
35
|
+
this.currentSegmentPath = path.join(this.directoryPath, getSegmentName(this.currentSegmentIndex, this.segmentPrefix));
|
|
36
|
+
this.currentSegmentSize = 0;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const lastSegment = segments[segments.length - 1];
|
|
40
|
+
this.currentSegmentIndex = lastSegment.index;
|
|
41
|
+
this.currentSegmentPath = lastSegment.filePath;
|
|
42
|
+
this.currentSegmentSize = (await fs.stat(lastSegment.filePath)).size;
|
|
43
|
+
}
|
|
44
|
+
this.initialized = true;
|
|
45
|
+
}
|
|
46
|
+
async rotateSegment() {
|
|
47
|
+
this.currentSegmentIndex += 1;
|
|
48
|
+
this.currentSegmentPath = path.join(this.directoryPath, getSegmentName(this.currentSegmentIndex, this.segmentPrefix));
|
|
49
|
+
this.currentSegmentSize = 0;
|
|
50
|
+
}
|
|
51
|
+
async appendFrame(frame) {
|
|
52
|
+
await this.ensureInitialized();
|
|
53
|
+
if (this.currentSegmentSize > 0 && this.currentSegmentSize + frame.length > this.maxSegmentBytes) {
|
|
54
|
+
await this.rotateSegment();
|
|
55
|
+
}
|
|
56
|
+
await fs.appendFile(this.currentSegmentPath, frame);
|
|
57
|
+
this.currentSegmentSize += frame.length;
|
|
58
|
+
}
|
|
59
|
+
}
|