@oas-tools/oas-telemetry 0.7.0-alpha.2 → 0.7.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +50 -17
- package/README.md +242 -240
- package/dist/cjs/config/bootConfig.cjs +18 -0
- package/dist/cjs/config/config.cjs +145 -0
- package/dist/cjs/config/config.types.cjs +5 -0
- package/dist/cjs/index.cjs +19 -25
- package/dist/cjs/{tlmRoutes.cjs → routesManager.cjs} +28 -21
- package/dist/cjs/{exporters/InMemoryLogRecordExporter.cjs → telemetry/custom-implementations/exporters/InMemoryDbLogExporter.cjs} +42 -19
- package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.cjs +97 -0
- package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.cjs +118 -0
- package/dist/cjs/telemetry/custom-implementations/exporters/PluginLogExporter.cjs +52 -0
- package/dist/cjs/telemetry/custom-implementations/exporters/PluginMetricExporter.cjs +53 -0
- package/dist/cjs/telemetry/custom-implementations/exporters/PluginSpanExporter.cjs +69 -0
- package/dist/cjs/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.cjs +70 -0
- package/dist/cjs/telemetry/custom-implementations/processors/dynamicMultiSpanProcessor.cjs +70 -0
- package/dist/cjs/{utils → telemetry/custom-implementations/utils}/circular.cjs +39 -49
- package/dist/cjs/telemetry/custom-implementations/wrappers.cjs +175 -0
- package/dist/cjs/telemetry/initializeTelemetry.cjs +74 -0
- package/dist/cjs/telemetry/telemetryConfigurator.cjs +84 -0
- package/dist/cjs/telemetry/telemetryRegistry.cjs +40 -0
- package/dist/cjs/tlm-ai/agent.cjs +82 -63
- package/dist/cjs/tlm-ai/aiController.cjs +5 -4
- package/dist/cjs/tlm-ai/aiRoutes.cjs +9 -6
- package/dist/cjs/tlm-ai/tools.cjs +16 -9
- package/dist/cjs/tlm-auth/authController.cjs +14 -15
- package/dist/cjs/tlm-auth/authMiddleware.cjs +11 -10
- package/dist/cjs/tlm-auth/authRoutes.cjs +9 -7
- package/dist/cjs/tlm-log/logController.cjs +45 -18
- package/dist/cjs/tlm-log/logRoutes.cjs +16 -11
- package/dist/cjs/tlm-metric/metricsController.cjs +37 -12
- package/dist/cjs/tlm-metric/metricsRoutes.cjs +16 -11
- package/dist/cjs/tlm-plugin/pluginController.cjs +40 -19
- package/dist/cjs/tlm-plugin/pluginRoutes.cjs +8 -6
- package/dist/cjs/tlm-plugin/pluginService.cjs +25 -0
- package/dist/cjs/tlm-trace/traceController.cjs +54 -45
- package/dist/cjs/tlm-trace/traceRoutes.cjs +16 -11
- package/dist/cjs/tlm-ui/uiRoutes.cjs +26 -20
- package/dist/cjs/tlm-util/utilController.cjs +8 -9
- package/dist/cjs/tlm-util/utilRoutes.cjs +22 -19
- package/dist/cjs/types/index.cjs +0 -1
- package/dist/cjs/utils/logger.cjs +3 -5
- package/dist/cjs/utils/regexUtils.cjs +27 -0
- package/dist/esm/config/bootConfig.js +11 -0
- package/dist/esm/config/config.js +126 -0
- package/dist/esm/index.js +18 -29
- package/dist/esm/{tlmRoutes.js → routesManager.js} +27 -22
- package/dist/esm/{exporters/InMemoryLogRecordExporter.js → telemetry/custom-implementations/exporters/InMemoryDbLogExporter.js} +31 -17
- package/dist/esm/{exporters/InMemoryDBMetricsExporter.js → telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.js} +31 -17
- package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.js +105 -0
- package/dist/esm/telemetry/custom-implementations/exporters/PluginLogExporter.js +44 -0
- package/dist/esm/telemetry/custom-implementations/exporters/PluginMetricExporter.js +43 -0
- package/dist/esm/telemetry/custom-implementations/exporters/PluginSpanExporter.js +61 -0
- package/dist/esm/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.js +64 -0
- package/dist/esm/telemetry/custom-implementations/processors/dynamicMultiSpanProcessor.js +64 -0
- package/dist/esm/telemetry/custom-implementations/utils/circular.js +76 -0
- package/dist/esm/telemetry/custom-implementations/wrappers.js +163 -0
- package/dist/esm/telemetry/initializeTelemetry.js +71 -0
- package/dist/esm/telemetry/telemetryConfigurator.js +74 -0
- package/dist/esm/telemetry/telemetryRegistry.js +34 -0
- package/dist/esm/tlm-ai/agent.js +77 -59
- package/dist/esm/tlm-ai/aiController.js +5 -4
- package/dist/esm/tlm-ai/aiRoutes.js +7 -5
- package/dist/esm/tlm-ai/tools.js +18 -9
- package/dist/esm/tlm-auth/authController.js +9 -10
- package/dist/esm/tlm-auth/authMiddleware.js +10 -9
- package/dist/esm/tlm-auth/authRoutes.js +8 -6
- package/dist/esm/tlm-log/logController.js +36 -16
- package/dist/esm/tlm-log/logRoutes.js +15 -11
- package/dist/esm/tlm-metric/metricsController.js +29 -10
- package/dist/esm/tlm-metric/metricsRoutes.js +15 -11
- package/dist/esm/tlm-plugin/pluginController.js +40 -19
- package/dist/esm/tlm-plugin/pluginRoutes.js +6 -5
- package/dist/esm/tlm-plugin/pluginService.js +19 -0
- package/dist/esm/tlm-trace/traceController.js +40 -35
- package/dist/esm/tlm-trace/traceRoutes.js +15 -11
- package/dist/esm/tlm-ui/uiRoutes.js +24 -19
- package/dist/esm/tlm-util/utilController.js +8 -9
- package/dist/esm/tlm-util/utilRoutes.js +17 -15
- package/dist/esm/types/index.js +0 -1
- package/dist/esm/utils/logger.js +3 -4
- package/dist/esm/utils/regexUtils.js +23 -0
- package/dist/types/config/bootConfig.d.ts +5 -0
- package/dist/types/config/config.d.ts +253 -0
- package/dist/types/config/config.types.d.ts +34 -0
- package/dist/types/index.d.ts +5 -4
- package/dist/types/routesManager.d.ts +3 -0
- package/dist/types/{exporters/InMemoryLogRecordExporter.d.ts → telemetry/custom-implementations/exporters/InMemoryDbLogExporter.d.ts} +6 -6
- package/dist/types/{exporters/InMemoryDBMetricsExporter.d.ts → telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.d.ts} +7 -7
- package/dist/types/{exporters/InMemoryDbExporter.d.ts → telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.d.ts} +9 -9
- package/dist/types/telemetry/custom-implementations/exporters/PluginLogExporter.d.ts +8 -0
- package/dist/types/telemetry/custom-implementations/exporters/PluginMetricExporter.d.ts +12 -0
- package/dist/types/telemetry/custom-implementations/exporters/PluginSpanExporter.d.ts +14 -0
- package/dist/types/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.d.ts +32 -0
- package/dist/types/telemetry/custom-implementations/processors/dynamicMultiSpanProcessor.d.ts +34 -0
- package/dist/types/telemetry/custom-implementations/utils/circular.d.ts +27 -0
- package/dist/types/telemetry/custom-implementations/wrappers.d.ts +52 -0
- package/dist/types/telemetry/initializeTelemetry.d.ts +1 -0
- package/dist/types/telemetry/telemetryConfigurator.d.ts +2 -0
- package/dist/types/telemetry/telemetryRegistry.d.ts +20 -0
- package/dist/types/tlm-ai/agent.d.ts +2 -1
- package/dist/types/tlm-ai/aiController.d.ts +4 -3
- package/dist/types/tlm-ai/aiRoutes.d.ts +2 -2
- package/dist/types/tlm-ai/tools.d.ts +3 -1
- package/dist/types/tlm-auth/authController.d.ts +4 -3
- package/dist/types/tlm-auth/authMiddleware.d.ts +2 -1
- package/dist/types/tlm-auth/authRoutes.d.ts +2 -2
- package/dist/types/tlm-log/logController.d.ts +1 -0
- package/dist/types/tlm-log/logRoutes.d.ts +2 -2
- package/dist/types/tlm-metric/metricsController.d.ts +1 -0
- package/dist/types/tlm-metric/metricsRoutes.d.ts +2 -2
- package/dist/types/tlm-plugin/pluginRoutes.d.ts +1 -2
- package/dist/types/tlm-plugin/pluginService.d.ts +9 -0
- package/dist/types/tlm-trace/traceController.d.ts +7 -6
- package/dist/types/tlm-trace/traceRoutes.d.ts +2 -2
- package/dist/types/tlm-ui/uiRoutes.d.ts +1 -2
- package/dist/types/tlm-util/utilController.d.ts +2 -1
- package/dist/types/tlm-util/utilRoutes.d.ts +2 -2
- package/dist/types/types/index.d.ts +7 -46
- package/dist/types/utils/regexUtils.d.ts +1 -0
- package/dist/ui/assets/index-D9HsRlaQ.js +437 -0
- package/dist/ui/assets/index-DEyIcKBi.css +1 -0
- package/dist/ui/index.html +3 -3
- package/dist/ui/oas-tlm.svg +185 -0
- package/package.json +12 -7
- package/dist/cjs/config.cjs +0 -31
- package/dist/cjs/exporters/InMemoryDBMetricsExporter.cjs +0 -74
- package/dist/cjs/exporters/InMemoryDbExporter.cjs +0 -102
- package/dist/cjs/exporters/consoleExporter.cjs +0 -47
- package/dist/cjs/exporters/dynamicExporter.cjs +0 -57
- package/dist/cjs/instrumentation/index.cjs +0 -28
- package/dist/cjs/instrumentation/logs.cjs +0 -46
- package/dist/cjs/instrumentation/metrics.cjs +0 -27
- package/dist/cjs/instrumentation/traces.cjs +0 -19
- package/dist/esm/config.js +0 -20
- package/dist/esm/exporters/InMemoryDbExporter.js +0 -102
- package/dist/esm/exporters/consoleExporter.js +0 -38
- package/dist/esm/exporters/dynamicExporter.js +0 -50
- package/dist/esm/instrumentation/index.js +0 -26
- package/dist/esm/instrumentation/logs.js +0 -34
- package/dist/esm/instrumentation/metrics.js +0 -18
- package/dist/esm/instrumentation/traces.js +0 -12
- package/dist/esm/utils/circular.js +0 -84
- package/dist/types/config.d.ts +0 -6
- package/dist/types/exporters/consoleExporter.d.ts +0 -13
- package/dist/types/exporters/dynamicExporter.d.ts +0 -25
- package/dist/types/instrumentation/logs.d.ts +0 -1
- package/dist/types/instrumentation/metrics.d.ts +0 -1
- package/dist/types/instrumentation/traces.d.ts +0 -1
- package/dist/types/tlmRoutes.d.ts +0 -2
- package/dist/types/utils/circular.d.ts +0 -31
- package/dist/ui/assets/index-BNhZBPi2.css +0 -1
- package/dist/ui/assets/index-DxGAMrAl.js +0 -401
- package/dist/ui/vite.svg +0 -1
- /package/dist/{types/instrumentation/index.d.ts → esm/config/config.types.js} +0 -0
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import { ExportResultCode } from '@opentelemetry/core';
|
|
2
2
|
import dataStore from '@seald-io/nedb';
|
|
3
3
|
import { applyNesting } from '../utils/circular.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { Enabler } from '../wrappers.js';
|
|
5
|
+
import logger from '../../../utils/logger.js';
|
|
6
|
+
export class InMemoryDbMetricExporter extends Enabler {
|
|
7
|
+
constructor(retentionTimeInSeconds = 3600) {
|
|
8
|
+
super();
|
|
9
|
+
this._retentionTimeInSeconds = retentionTimeInSeconds;
|
|
10
|
+
this._metrics = new dataStore({ timestampData: true });
|
|
11
|
+
this._metrics.ensureIndex({ fieldName: 'createdAt' });
|
|
12
|
+
this._startCleanupJob();
|
|
8
13
|
}
|
|
9
14
|
export(metrics, resultCallback) {
|
|
10
15
|
try {
|
|
11
|
-
if (
|
|
16
|
+
if (this.isEnabled()) {
|
|
12
17
|
const scopeMetrics = metrics?.scopeMetrics;
|
|
13
18
|
const cleanMetrics = applyNesting(scopeMetrics);
|
|
14
19
|
this._metrics.insert(cleanMetrics, (err, _newDoc) => {
|
|
15
20
|
if (err) {
|
|
16
|
-
|
|
21
|
+
logger.error('Insertion Error:', err);
|
|
17
22
|
return;
|
|
18
23
|
}
|
|
19
24
|
});
|
|
@@ -21,24 +26,15 @@ export class InMemoryDBMetricsExporter {
|
|
|
21
26
|
setTimeout(() => resultCallback({ code: ExportResultCode.SUCCESS }), 0);
|
|
22
27
|
}
|
|
23
28
|
catch (error) {
|
|
24
|
-
|
|
29
|
+
logger.error('Error exporting metrics\n' + error.message + '\n' + error.stack);
|
|
25
30
|
return resultCallback({
|
|
26
31
|
code: ExportResultCode.FAILED,
|
|
27
32
|
error: new Error('Error exporting metrics\n' + error.message + '\n' + error.stack),
|
|
28
33
|
});
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
|
-
start() {
|
|
32
|
-
this._stopped = false;
|
|
33
|
-
}
|
|
34
|
-
stop() {
|
|
35
|
-
this._stopped = true;
|
|
36
|
-
}
|
|
37
|
-
isRunning() {
|
|
38
|
-
return !this._stopped;
|
|
39
|
-
}
|
|
40
36
|
shutdown() {
|
|
41
|
-
this.
|
|
37
|
+
this._enabled = false;
|
|
42
38
|
this._metrics = new dataStore();
|
|
43
39
|
return this.forceFlush();
|
|
44
40
|
}
|
|
@@ -62,4 +58,22 @@ export class InMemoryDBMetricsExporter {
|
|
|
62
58
|
insert(metrics, callback) {
|
|
63
59
|
this._metrics.insert(metrics, callback);
|
|
64
60
|
}
|
|
61
|
+
set retentionTimeInSeconds(retentionTimeInSeconds) {
|
|
62
|
+
this._retentionTimeInSeconds = retentionTimeInSeconds;
|
|
63
|
+
logger.info(`InMemoryDbMetricExporter retention time set to ${this._retentionTimeInSeconds} seconds`);
|
|
64
|
+
}
|
|
65
|
+
_startCleanupJob() {
|
|
66
|
+
const interval = 1000;
|
|
67
|
+
setInterval(() => {
|
|
68
|
+
const expirationDate = new Date(Date.now() - this._retentionTimeInSeconds * 1000);
|
|
69
|
+
this._metrics.remove({ createdAt: { $lt: expirationDate } }, { multi: true }, (err, numRemoved) => {
|
|
70
|
+
if (err) {
|
|
71
|
+
logger.error('Error in TTL cleanup:', err);
|
|
72
|
+
}
|
|
73
|
+
else if (numRemoved > 0) {
|
|
74
|
+
logger.debug(`TTL cleanup: removed ${numRemoved} expired metrics`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}, interval);
|
|
78
|
+
}
|
|
65
79
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ExportResultCode } from '@opentelemetry/core';
|
|
2
|
+
import dataStore from '@seald-io/nedb';
|
|
3
|
+
import logger from '../../../utils/logger.js';
|
|
4
|
+
import { applyNesting, removeCircularRefs } from '../utils/circular.js';
|
|
5
|
+
import { Enabler } from '../wrappers.js';
|
|
6
|
+
export class InMemoryDbSpanExporter extends Enabler {
|
|
7
|
+
constructor(retentionTimeInSeconds = 3600) {
|
|
8
|
+
super();
|
|
9
|
+
this._baseUrl = '/telemetry'; // Default base URL, can be overridden by the config
|
|
10
|
+
this._retentionTimeInSeconds = retentionTimeInSeconds;
|
|
11
|
+
this._spans = new dataStore({ timestampData: true });
|
|
12
|
+
this._spans.ensureIndex({ fieldName: 'createdAt' });
|
|
13
|
+
this._startCleanupJob();
|
|
14
|
+
}
|
|
15
|
+
;
|
|
16
|
+
set baseUrl(baseUrl) {
|
|
17
|
+
this._baseUrl = baseUrl;
|
|
18
|
+
}
|
|
19
|
+
set retentionTimeInSeconds(retentionTimeInSeconds) {
|
|
20
|
+
this._retentionTimeInSeconds = retentionTimeInSeconds;
|
|
21
|
+
logger.info(`InMemoryDbSpanExporter retention time set to ${this._retentionTimeInSeconds} seconds`);
|
|
22
|
+
}
|
|
23
|
+
export(readableSpans, resultCallback) {
|
|
24
|
+
logger.debug('InMemoryDbSpanExporter.export called with spans: ', readableSpans.length);
|
|
25
|
+
try {
|
|
26
|
+
if (!this.isEnabled()) {
|
|
27
|
+
logger.debug('InMemoryDbSpanExporter is not enabled. Skipping export.');
|
|
28
|
+
return resultCallback({ code: ExportResultCode.SUCCESS });
|
|
29
|
+
}
|
|
30
|
+
// Prepare spans to be inserted into the in-memory database (remove circular references and convert to nested objects)
|
|
31
|
+
const cleanSpans = readableSpans
|
|
32
|
+
.map(nestedSpan => removeCircularRefs(nestedSpan)) // to avoid JSON parsing error
|
|
33
|
+
.map(span => applyNesting(span)) // to avoid dot notation in keys (neDB does not support dot notation in keys)
|
|
34
|
+
.filter(span => {
|
|
35
|
+
const target = span?.attributes?.http?.target; // Exclude spans where target includes 'telemetry' but NOT 'telemetry/utils'
|
|
36
|
+
if (target && target.includes(this._baseUrl)) {
|
|
37
|
+
return target.includes(this._baseUrl + '/utils');
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
});
|
|
41
|
+
// Insert spans into the in-memory database
|
|
42
|
+
this._spans.insert(cleanSpans, (err, _newDoc) => {
|
|
43
|
+
if (err) {
|
|
44
|
+
logger.error(err);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
setTimeout(() => resultCallback({ code: ExportResultCode.SUCCESS }), 0);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
logger.error('Error exporting spans\n' + error.message + '\n' + error.stack);
|
|
52
|
+
return resultCallback({
|
|
53
|
+
code: ExportResultCode.FAILED,
|
|
54
|
+
error: new Error('Error exporting spans\n' + error.message + '\n' + error.stack),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
;
|
|
59
|
+
shutdown() {
|
|
60
|
+
this._spans = new dataStore();
|
|
61
|
+
return this.forceFlush();
|
|
62
|
+
}
|
|
63
|
+
;
|
|
64
|
+
/**
|
|
65
|
+
* Exports any pending spans in the exporter
|
|
66
|
+
*/
|
|
67
|
+
forceFlush() {
|
|
68
|
+
return Promise.resolve();
|
|
69
|
+
}
|
|
70
|
+
;
|
|
71
|
+
//err,docs
|
|
72
|
+
find(search, callback) {
|
|
73
|
+
this._spans.find(search, callback);
|
|
74
|
+
}
|
|
75
|
+
reset() {
|
|
76
|
+
this._spans = new dataStore();
|
|
77
|
+
}
|
|
78
|
+
;
|
|
79
|
+
getFinishedSpans() {
|
|
80
|
+
return this._spans.getAllData();
|
|
81
|
+
}
|
|
82
|
+
;
|
|
83
|
+
/**
|
|
84
|
+
* Inserts spans into the in-memory database.
|
|
85
|
+
* @param spans - The spans to insert.
|
|
86
|
+
* @param callback - The callback to execute after insertion.
|
|
87
|
+
*/
|
|
88
|
+
insert(spans, callback) {
|
|
89
|
+
this._spans.insert(spans, callback);
|
|
90
|
+
}
|
|
91
|
+
_startCleanupJob() {
|
|
92
|
+
const interval = 1000;
|
|
93
|
+
setInterval(() => {
|
|
94
|
+
const expirationDate = new Date(Date.now() - this._retentionTimeInSeconds * 1000);
|
|
95
|
+
this._spans.remove({ createdAt: { $lt: expirationDate } }, { multi: true }, (err, numRemoved) => {
|
|
96
|
+
if (err) {
|
|
97
|
+
logger.error('Error in TTL cleanup:', err);
|
|
98
|
+
}
|
|
99
|
+
else if (numRemoved > 0) {
|
|
100
|
+
logger.debug(`TTL cleanup: removed ${numRemoved} expired spans`);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}, interval);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ExportResultCode } from '@opentelemetry/core';
|
|
2
|
+
import logger from '../../../utils/logger.js';
|
|
3
|
+
import { Enabler } from '../wrappers.js';
|
|
4
|
+
import { pluginService } from '../../../tlm-plugin/pluginService.js';
|
|
5
|
+
import { applyNesting, removeCircularRefs } from '../utils/circular.js';
|
|
6
|
+
export class PluginLogExporter extends Enabler {
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
}
|
|
10
|
+
export(logs, resultCallback) {
|
|
11
|
+
logger.debug('PluginLogExporter.export called with logs: ', logs.length);
|
|
12
|
+
try {
|
|
13
|
+
if (!this.isEnabled()) {
|
|
14
|
+
logger.debug('PluginLogExporter is not enabled. Skipping export.');
|
|
15
|
+
return resultCallback({ code: ExportResultCode.SUCCESS });
|
|
16
|
+
}
|
|
17
|
+
const cleanLogs = logs
|
|
18
|
+
.map(log => removeCircularRefs(log))
|
|
19
|
+
.map(log => applyNesting(log));
|
|
20
|
+
pluginService.getPlugins().forEach((pluginResource, i) => {
|
|
21
|
+
if (typeof pluginResource.pluginImplementation.newLog === 'function') {
|
|
22
|
+
cleanLogs.forEach((log) => {
|
|
23
|
+
logger.debug(`Sending log to plugin (Plugin #${i}) <${pluginResource.name}>`);
|
|
24
|
+
pluginResource.pluginImplementation.newLog(log);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
logger.debug(`Plugin <${pluginResource.name}> does not implement newLog method. Skipping log export.`);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
setTimeout(() => resultCallback({ code: ExportResultCode.SUCCESS }), 0);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
logger.error('Error exporting logs\n' + error.message + '\n' + error.stack);
|
|
35
|
+
return resultCallback({
|
|
36
|
+
code: ExportResultCode.FAILED,
|
|
37
|
+
error: new Error('Error exporting logs\n' + error.message + '\n' + error.stack),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
shutdown() {
|
|
42
|
+
return Promise.resolve();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ExportResultCode } from '@opentelemetry/core';
|
|
2
|
+
import logger from '../../../utils/logger.js';
|
|
3
|
+
import { Enabler } from '../wrappers.js';
|
|
4
|
+
import { pluginService } from '../../../tlm-plugin/pluginService.js';
|
|
5
|
+
import { applyNesting, removeCircularRefs } from '../utils/circular.js';
|
|
6
|
+
export class PluginMetricExporter extends Enabler {
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
}
|
|
10
|
+
export(metrics, resultCallback) {
|
|
11
|
+
logger.debug('PluginMetricExporter.export called');
|
|
12
|
+
try {
|
|
13
|
+
if (!this.isEnabled()) {
|
|
14
|
+
logger.debug('PluginMetricExporter is not enabled. Skipping export.');
|
|
15
|
+
return resultCallback({ code: ExportResultCode.SUCCESS });
|
|
16
|
+
}
|
|
17
|
+
const cleanMetrics = applyNesting(removeCircularRefs(metrics));
|
|
18
|
+
pluginService.getPlugins().forEach((pluginResource, i) => {
|
|
19
|
+
if (typeof pluginResource.pluginImplementation.newMetric === 'function') {
|
|
20
|
+
logger.debug(`Sending metric to plugin (Plugin #${i}) <${pluginResource.name}>`);
|
|
21
|
+
pluginResource.pluginImplementation.newMetric(cleanMetrics);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
logger.debug(`Plugin <${pluginResource.name}> does not implement newMetric method. Skipping metric export.`);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
setTimeout(() => resultCallback({ code: ExportResultCode.SUCCESS }), 0);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
logger.error('Error exporting metrics\n' + error.message + '\n' + error.stack);
|
|
31
|
+
return resultCallback({
|
|
32
|
+
code: ExportResultCode.FAILED,
|
|
33
|
+
error: new Error('Error exporting metrics\n' + error.message + '\n' + error.stack),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
shutdown() {
|
|
38
|
+
return Promise.resolve();
|
|
39
|
+
}
|
|
40
|
+
forceFlush() {
|
|
41
|
+
return Promise.resolve();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ExportResultCode } from '@opentelemetry/core';
|
|
2
|
+
import logger from '../../../utils/logger.js';
|
|
3
|
+
import { Enabler } from '../wrappers.js';
|
|
4
|
+
import { pluginService } from '../../../tlm-plugin/pluginService.js';
|
|
5
|
+
import { applyNesting, removeCircularRefs } from '../utils/circular.js';
|
|
6
|
+
export class PluginSpanExporter extends Enabler {
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
this._baseUrl = '/telemetry'; // Default base URL, can be overridden by the config
|
|
10
|
+
}
|
|
11
|
+
;
|
|
12
|
+
set baseUrl(baseUrl) {
|
|
13
|
+
this._baseUrl = baseUrl;
|
|
14
|
+
}
|
|
15
|
+
export(readableSpans, resultCallback) {
|
|
16
|
+
logger.debug('PluginSpanExporter.export called with spans: ', readableSpans.length);
|
|
17
|
+
try {
|
|
18
|
+
if (!this.isEnabled()) {
|
|
19
|
+
logger.debug('PluginSpanExporter is not enabled. Skipping export.');
|
|
20
|
+
return resultCallback({ code: ExportResultCode.SUCCESS });
|
|
21
|
+
}
|
|
22
|
+
// Prepare spans to be inserted into the in-memory database (remove circular references and convert to nested objects)
|
|
23
|
+
const cleanSpans = readableSpans
|
|
24
|
+
.map(nestedSpan => removeCircularRefs(nestedSpan)) // to avoid JSON parsing error
|
|
25
|
+
.map(span => applyNesting(span)) // to avoid dot notation in keys (neDB does not support dot notation in keys)
|
|
26
|
+
.filter(span => {
|
|
27
|
+
const target = span?.attributes?.http?.target; // Exclude spans where target includes 'telemetry' but NOT 'telemetry/utils'
|
|
28
|
+
if (target && target.includes(this._baseUrl)) {
|
|
29
|
+
return target.includes(this._baseUrl + '/utils');
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
});
|
|
33
|
+
pluginService.getPlugins().forEach((pluginResource, i) => {
|
|
34
|
+
if (typeof pluginResource.pluginImplementation.newTrace === 'function') {
|
|
35
|
+
cleanSpans.forEach((span) => {
|
|
36
|
+
logger.debug(`Sending span to plugin (Plugin #${i}) <${pluginResource.name}>`);
|
|
37
|
+
//TODO: This should be called newSpan instead of newTrace
|
|
38
|
+
pluginResource.pluginImplementation.newTrace(span);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
logger.debug(`Plugin <${pluginResource.name}> does not implement newTrace method. Skipping span export.`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
setTimeout(() => resultCallback({ code: ExportResultCode.SUCCESS }), 0);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
logger.error('Error exporting spans\n' + error.message + '\n' + error.stack);
|
|
49
|
+
return resultCallback({
|
|
50
|
+
code: ExportResultCode.FAILED,
|
|
51
|
+
error: new Error('Error exporting spans\n' + error.message + '\n' + error.stack),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
shutdown() {
|
|
56
|
+
return this.forceFlush();
|
|
57
|
+
}
|
|
58
|
+
forceFlush() {
|
|
59
|
+
return Promise.resolve();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { callWithTimeout } from '@opentelemetry/core';
|
|
2
|
+
import logger from '../../../utils/logger.js'; // optional if you want logging
|
|
3
|
+
export class DynamicMultiLogRecordProcessor {
|
|
4
|
+
constructor(initialProcessors = [], forceFlushTimeoutMillis = 30000) {
|
|
5
|
+
this._processors = [];
|
|
6
|
+
this._processors = [...initialProcessors];
|
|
7
|
+
this._forceFlushTimeoutMillis = forceFlushTimeoutMillis;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Add a new LogRecordProcessor or an array of LogRecordProcessors at runtime.
|
|
11
|
+
*/
|
|
12
|
+
addProcessors(processor) {
|
|
13
|
+
if (Array.isArray(processor)) {
|
|
14
|
+
this._processors.push(...processor);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
this._processors.push(processor);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Remove a specific LogRecordProcessor.
|
|
22
|
+
*/
|
|
23
|
+
removeProcessor(processor) {
|
|
24
|
+
this._processors = this._processors.filter(p => p !== processor);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Clear all LogRecordProcessors.
|
|
28
|
+
*/
|
|
29
|
+
clearProcessors() {
|
|
30
|
+
this._processors = [];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Called when a log record is emitted.
|
|
34
|
+
*/
|
|
35
|
+
onEmit(logRecord, context) {
|
|
36
|
+
for (const processor of this._processors) {
|
|
37
|
+
try {
|
|
38
|
+
processor.onEmit(logRecord, context);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
logger?.error?.('Error in onEmit of LogRecordProcessor:', error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Force flush all processors with timeout.
|
|
47
|
+
*/
|
|
48
|
+
async forceFlush() {
|
|
49
|
+
const timeout = this._forceFlushTimeoutMillis;
|
|
50
|
+
const promises = this._processors.map(p => callWithTimeout(p.forceFlush(), timeout));
|
|
51
|
+
try {
|
|
52
|
+
await Promise.all(promises);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
logger?.error?.('Error during forceFlush in DynamicMultiLogRecordProcessor:', error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Shutdown all processors.
|
|
60
|
+
*/
|
|
61
|
+
async shutdown() {
|
|
62
|
+
await Promise.all(this._processors.map(p => p.shutdown()));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import logger from '../../../utils/logger.js';
|
|
2
|
+
export class DynamicMultiSpanProcessor {
|
|
3
|
+
constructor(initialProcessors = []) {
|
|
4
|
+
this._spanProcessors = [];
|
|
5
|
+
this._spanProcessors = [...initialProcessors];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Add a new SpanProcessor or an array of SpanProcessors at runtime.
|
|
9
|
+
*/
|
|
10
|
+
addProcessors(processor) {
|
|
11
|
+
if (Array.isArray(processor)) {
|
|
12
|
+
this._spanProcessors.push(...processor);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
this._spanProcessors.push(processor);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Remove a specific SpanProcessor if needed.
|
|
20
|
+
*/
|
|
21
|
+
removeProcessor(processor) {
|
|
22
|
+
this._spanProcessors = this._spanProcessors.filter(p => p !== processor);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Clear all processors.
|
|
26
|
+
*/
|
|
27
|
+
clearProcessors() {
|
|
28
|
+
this._spanProcessors = [];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Called when a span is started.
|
|
32
|
+
*/
|
|
33
|
+
onStart(span, context) {
|
|
34
|
+
for (const processor of this._spanProcessors) {
|
|
35
|
+
processor.onStart(span, context);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Called when a span ends.
|
|
40
|
+
*/
|
|
41
|
+
onEnd(span) {
|
|
42
|
+
for (const processor of this._spanProcessors) {
|
|
43
|
+
processor.onEnd(span);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Force flush all processors.
|
|
48
|
+
*/
|
|
49
|
+
async forceFlush() {
|
|
50
|
+
const promises = this._spanProcessors.map(p => p.forceFlush());
|
|
51
|
+
try {
|
|
52
|
+
await Promise.all(promises);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
logger.error('Error during forceFlush:', error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Shutdown all processors.
|
|
60
|
+
*/
|
|
61
|
+
async shutdown() {
|
|
62
|
+
await Promise.all(this._spanProcessors.map(p => p.shutdown()));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export function removeCircularRefs(obj) {
|
|
2
|
+
// const seen = new WeakMap(); // Used to keep track of visited objects
|
|
3
|
+
// Replacer function to handle circular references
|
|
4
|
+
function replacer(key, value) {
|
|
5
|
+
if (key === "_spanProcessor") {
|
|
6
|
+
return "oas-telemetry skips this field to avoid circular reference";
|
|
7
|
+
}
|
|
8
|
+
// GENERIC CIRCULAR REFERENCE HANDLING
|
|
9
|
+
// if (typeof value === "object" && value !== null) {
|
|
10
|
+
// // If the object has been visited before, return the name prefixed with "CIRCULAR+"
|
|
11
|
+
// if (seen.has(value)) {
|
|
12
|
+
// return `CIRCULAR${key}`;
|
|
13
|
+
// }
|
|
14
|
+
// seen.set(value, key); // Mark the object as visited with its name
|
|
15
|
+
// }
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
// Convert the object to a string and then parse it back
|
|
19
|
+
// This will trigger the replacer function to handle circular references
|
|
20
|
+
const jsonString = JSON.stringify(obj, replacer);
|
|
21
|
+
return JSON.parse(jsonString);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Recursively converts dot-separated keys in an object to nested objects.
|
|
25
|
+
*
|
|
26
|
+
* @param {any} obj - The object to process.
|
|
27
|
+
* @returns {any} - The object with all dot-separated keys converted to nested objects.
|
|
28
|
+
* @example
|
|
29
|
+
* Input:
|
|
30
|
+
* {
|
|
31
|
+
* "http.method": "GET",
|
|
32
|
+
* "http.url": "http://example.com",
|
|
33
|
+
* "nested.obj.key": "value"
|
|
34
|
+
* }
|
|
35
|
+
* Output:
|
|
36
|
+
* {
|
|
37
|
+
* "http": {
|
|
38
|
+
* "method": "GET",
|
|
39
|
+
* "url": "http://example.com"
|
|
40
|
+
* },
|
|
41
|
+
* "nested": {
|
|
42
|
+
* "obj": {
|
|
43
|
+
* "key": "value"
|
|
44
|
+
* }
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
*/
|
|
48
|
+
export function applyNesting(obj) {
|
|
49
|
+
if (Array.isArray(obj)) {
|
|
50
|
+
return obj.map(item => applyNesting(item));
|
|
51
|
+
}
|
|
52
|
+
else if (typeof obj === 'object' && obj !== null) {
|
|
53
|
+
const result = {};
|
|
54
|
+
for (const key in obj) {
|
|
55
|
+
const value = applyNesting(obj[key]);
|
|
56
|
+
const keys = key.split('.');
|
|
57
|
+
let temp = result;
|
|
58
|
+
for (let i = 0; i < keys.length; i++) {
|
|
59
|
+
const currentKey = keys[i];
|
|
60
|
+
if (i === keys.length - 1) {
|
|
61
|
+
temp[currentKey] = value;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
if (!temp[currentKey]) {
|
|
65
|
+
temp[currentKey] = {};
|
|
66
|
+
}
|
|
67
|
+
temp = temp[currentKey];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
return obj;
|
|
75
|
+
}
|
|
76
|
+
}
|