@oas-tools/oas-telemetry 0.1.5 → 0.1.6
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/LICENSE +200 -200
- package/README.md +98 -98
- package/dist/exporters/InMemoryDbExporter.cjs +2 -2
- package/dist/index.cjs +83 -6
- package/package.json +51 -46
- package/src/exporters/InMemoryDbExporter.js +89 -89
- package/src/index.js +196 -97
- package/src/telemetry.js +47 -47
- package/src/ui/detail.html +441 -0
- package/src/ui/main.html +266 -0
package/dist/index.cjs
CHANGED
|
@@ -7,36 +7,58 @@ exports.default = oasTelemetry;
|
|
|
7
7
|
var _telemetry = require("./telemetry.cjs");
|
|
8
8
|
var _express = require("express");
|
|
9
9
|
var _v = _interopRequireDefault(require("v8"));
|
|
10
|
+
var _fs = require("fs");
|
|
11
|
+
var _path = _interopRequireDefault(require("path"));
|
|
12
|
+
var _jsYaml = _interopRequireDefault(require("js-yaml"));
|
|
13
|
+
var _url = require("url");
|
|
10
14
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
15
|
// telemetryMiddleware.js
|
|
12
16
|
|
|
17
|
+
const _filename = (0, _url.fileURLToPath)(import.meta.url);
|
|
18
|
+
const _dirname = _path.default.dirname(_filename);
|
|
19
|
+
let telemetryStatus = {
|
|
20
|
+
active: true
|
|
21
|
+
};
|
|
22
|
+
let baseURL = '/telemetry';
|
|
13
23
|
let telemetryConfig = {
|
|
14
24
|
exporter: _telemetry.inMemoryExporter,
|
|
15
|
-
|
|
25
|
+
specFileName: ""
|
|
16
26
|
};
|
|
17
27
|
function oasTelemetry(tlConfig) {
|
|
18
28
|
if (tlConfig) {
|
|
19
29
|
console.log('Telemetry config provided');
|
|
20
30
|
telemetryConfig = tlConfig;
|
|
31
|
+
if (telemetryConfig.exporter == undefined) telemetryConfig.exporter = _telemetry.inMemoryExporter;
|
|
32
|
+
}
|
|
33
|
+
if (telemetryConfig.spec) console.log(`Spec content provided`);else {
|
|
34
|
+
if (telemetryConfig.specFileName != "") console.log(`Spec file used for telemetry: ${telemetryConfig.specFileName}`);else {
|
|
35
|
+
console.log("No spec available !");
|
|
36
|
+
}
|
|
21
37
|
}
|
|
22
38
|
const router = (0, _express.Router)();
|
|
23
|
-
|
|
24
|
-
|
|
39
|
+
|
|
40
|
+
//const baseURL = telemetryConfig.baseURL;
|
|
41
|
+
|
|
42
|
+
router.get(baseURL, mainPage);
|
|
43
|
+
router.get(baseURL + "/detail/*", detailPage);
|
|
44
|
+
router.get(baseURL + "/spec", specLoader);
|
|
45
|
+
router.get(baseURL + "/api", apiPage);
|
|
25
46
|
router.get(baseURL + "/reset", resetTelemetry);
|
|
26
47
|
router.get(baseURL + "/start", startTelemetry);
|
|
27
48
|
router.get(baseURL + "/stop", stopTelemetry);
|
|
49
|
+
router.get(baseURL + "/status", statusTelemetry);
|
|
28
50
|
router.get(baseURL + "/list", listTelemetry);
|
|
29
51
|
router.post(baseURL + "/find", findTelemetry);
|
|
30
52
|
router.get(baseURL + "/heapStats", heapStats);
|
|
31
53
|
return router;
|
|
32
54
|
}
|
|
33
|
-
const
|
|
55
|
+
const apiPage = (req, res) => {
|
|
34
56
|
let text = `
|
|
35
|
-
<h1>Telemetry
|
|
36
|
-
<h2>Available routes:</h2>
|
|
57
|
+
<h1>Telemetry API routes:</h1>
|
|
37
58
|
<ul>
|
|
38
59
|
<li><a href="/telemetry/start">/telemetry/start</a></li>
|
|
39
60
|
<li><a href="/telemetry/stop">/telemetry/stop</a></li>
|
|
61
|
+
<li><a href="/telemetry/status">/telemetry/status</a></li>
|
|
40
62
|
<li><a href="/telemetry/reset">/telemetry/reset</a></li>
|
|
41
63
|
<li><a href="/telemetry/list">/telemetry/list</a></li>
|
|
42
64
|
<li><a href="/telemetry/heapStats">/telemetry/heapStats</a></li>
|
|
@@ -45,14 +67,69 @@ const landingPage = (req, res) => {
|
|
|
45
67
|
`;
|
|
46
68
|
res.send(text);
|
|
47
69
|
};
|
|
70
|
+
const mainPage = (req, res) => {
|
|
71
|
+
const data = (0, _fs.readFileSync)(_dirname + '/ui/main.html', {
|
|
72
|
+
encoding: 'utf8',
|
|
73
|
+
flag: 'r'
|
|
74
|
+
});
|
|
75
|
+
res.send(data);
|
|
76
|
+
};
|
|
77
|
+
const detailPage = (req, res) => {
|
|
78
|
+
const data = (0, _fs.readFileSync)(_dirname + '/ui/detail.html', {
|
|
79
|
+
encoding: 'utf8',
|
|
80
|
+
flag: 'r'
|
|
81
|
+
});
|
|
82
|
+
res.send(data);
|
|
83
|
+
};
|
|
84
|
+
const specLoader = (req, res) => {
|
|
85
|
+
if (telemetryConfig.specFileName) {
|
|
86
|
+
try {
|
|
87
|
+
const data = (0, _fs.readFileSync)(telemetryConfig.specFileName, {
|
|
88
|
+
encoding: 'utf8',
|
|
89
|
+
flag: 'r'
|
|
90
|
+
});
|
|
91
|
+
const extension = _path.default.extname(telemetryConfig.specFileName);
|
|
92
|
+
let json = data;
|
|
93
|
+
if (extension == _jsYaml.default) json = JSON.stringify(_jsYaml.default.SafeLoad(data), null, 2);
|
|
94
|
+
res.setHeader('Content-Type', 'application/json');
|
|
95
|
+
res.send(json);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.log(`ERROR loading spec file ${telemetryConfig.specFileName}: ${e}`);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
if (telemetryConfig.spec) {
|
|
101
|
+
let spec = false;
|
|
102
|
+
try {
|
|
103
|
+
spec = JSON.parse(telemetryConfig.spec);
|
|
104
|
+
} catch (ej) {
|
|
105
|
+
try {
|
|
106
|
+
spec = JSON.stringify(_jsYaml.default.load(telemetryConfig.spec), null, 2);
|
|
107
|
+
} catch (ey) {
|
|
108
|
+
console.log(`Error parsing spec: ${ej} - ${ey}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!spec) {
|
|
112
|
+
res.status(404);
|
|
113
|
+
} else {
|
|
114
|
+
res.setHeader('Content-Type', 'application/json');
|
|
115
|
+
res.send(spec);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
48
120
|
const startTelemetry = (req, res) => {
|
|
49
121
|
telemetryConfig.exporter.start();
|
|
122
|
+
telemetryStatus.active = true;
|
|
50
123
|
res.send('Telemetry started');
|
|
51
124
|
};
|
|
52
125
|
const stopTelemetry = (req, res) => {
|
|
53
126
|
telemetryConfig.exporter.stop();
|
|
127
|
+
telemetryStatus.active = false;
|
|
54
128
|
res.send('Telemetry stopped');
|
|
55
129
|
};
|
|
130
|
+
const statusTelemetry = (req, res) => {
|
|
131
|
+
res.send(telemetryStatus);
|
|
132
|
+
};
|
|
56
133
|
const resetTelemetry = (req, res) => {
|
|
57
134
|
telemetryConfig.exporter.reset();
|
|
58
135
|
res.send('Telemetry reset');
|
package/package.json
CHANGED
|
@@ -1,46 +1,51 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@oas-tools/oas-telemetry",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "This package exports an express midelware that allows to trace the requests and responses of an express application using OpenTelemetry",
|
|
5
|
-
"author": "Manuel Otero",
|
|
6
|
-
"contributors": [
|
|
7
|
-
"Alejandro Santisteban"
|
|
8
|
-
],
|
|
9
|
-
"license": "Apache-2.0",
|
|
10
|
-
"type": "module",
|
|
11
|
-
"scripts": {
|
|
12
|
-
"build": "babel src -d dist --out-file-extension .cjs",
|
|
13
|
-
"publish": "npm run build && npm publish"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"@opentelemetry/
|
|
29
|
-
"@opentelemetry/
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"@babel/
|
|
40
|
-
"@
|
|
41
|
-
"@
|
|
42
|
-
"babel
|
|
43
|
-
"babel-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@oas-tools/oas-telemetry",
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"description": "This package exports an express midelware that allows to trace the requests and responses of an express application using OpenTelemetry",
|
|
5
|
+
"author": "Manuel Otero",
|
|
6
|
+
"contributors": [
|
|
7
|
+
"Alejandro Santisteban","Pablo Fernandez"
|
|
8
|
+
],
|
|
9
|
+
"license": "Apache-2.0",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "babel src -d dist --out-file-extension .cjs",
|
|
13
|
+
"publish": "npm run build && npm publish",
|
|
14
|
+
"test": "npm i && cd test/performance/ && ./test.sh"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src"
|
|
19
|
+
],
|
|
20
|
+
"main": "./dist/index.cjs",
|
|
21
|
+
"module": "./src/index.js",
|
|
22
|
+
"exports": {
|
|
23
|
+
"require": "./dist/index.cjs",
|
|
24
|
+
"import": "./src/index.js",
|
|
25
|
+
"default": "./src/index.js"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@opentelemetry/instrumentation-http": "^0.51.0",
|
|
29
|
+
"@opentelemetry/resources": "^1.24.0",
|
|
30
|
+
"@opentelemetry/sdk-node": "^0.49.1",
|
|
31
|
+
"axios": "^1.6.8",
|
|
32
|
+
"express": "^4.19.2",
|
|
33
|
+
"js-yaml": "^4.1.0",
|
|
34
|
+
"nedb": "^1.8.0",
|
|
35
|
+
"readline": "^1.3.0",
|
|
36
|
+
"v8": "^0.1.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@babel/cli": "^7.24.1",
|
|
40
|
+
"@babel/core": "^7.24.4",
|
|
41
|
+
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
42
|
+
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
|
|
43
|
+
"@babel/preset-env": "^7.24.4",
|
|
44
|
+
"@opentelemetry/api": "^1.8.0",
|
|
45
|
+
"@opentelemetry/auto-instrumentations-node": "^0.43.0",
|
|
46
|
+
"apipecker": "^1.3.1",
|
|
47
|
+
"babel-plugin-add-module-exports": "^1.0.4",
|
|
48
|
+
"babel-plugin-module-extension": "^0.1.3",
|
|
49
|
+
"nodemon": "^3.1.0"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
import { ExportResultCode } from '@opentelemetry/core';
|
|
2
|
-
|
|
3
|
-
//import in memory database
|
|
4
|
-
import dataStore from 'nedb'
|
|
5
|
-
|
|
6
|
-
export class InMemoryExporter {
|
|
7
|
-
constructor() {
|
|
8
|
-
this._spans = new dataStore();
|
|
9
|
-
this._stopped = false;
|
|
10
|
-
}
|
|
11
|
-
export(readableSpans, resultCallback) {
|
|
12
|
-
try {
|
|
13
|
-
if (!this._stopped) {
|
|
14
|
-
// Remove circular references
|
|
15
|
-
const cleanSpans = readableSpans.map(span => removeCircular(span));
|
|
16
|
-
|
|
17
|
-
// Insert spans into the in-memory database
|
|
18
|
-
this._spans.insert(cleanSpans, (err, newDoc) => {
|
|
19
|
-
if (err) {
|
|
20
|
-
console.error(err);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
setTimeout(() => resultCallback({ code: ExportResultCode.SUCCESS }), 0);
|
|
27
|
-
} catch (error) {
|
|
28
|
-
console.error('Error exporting spans\n' + error.message + '\n' + error.stack);
|
|
29
|
-
return resultCallback({
|
|
30
|
-
code: ExportResultCode.FAILED,
|
|
31
|
-
error: new Error('Error exporting spans\n' + error.message + '\n' + error.stack),
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
start() {
|
|
36
|
-
this._stopped = false;
|
|
37
|
-
}
|
|
38
|
-
stop() {
|
|
39
|
-
this._stopped = true;
|
|
40
|
-
}
|
|
41
|
-
shutdown() {
|
|
42
|
-
this._stopped = true;
|
|
43
|
-
this._spans = new dataStore();
|
|
44
|
-
return this.forceFlush();
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Exports any pending spans in the exporter
|
|
48
|
-
*/
|
|
49
|
-
forceFlush() {
|
|
50
|
-
return Promise.resolve();
|
|
51
|
-
}
|
|
52
|
-
reset() {
|
|
53
|
-
this._spans = new dataStore();
|
|
54
|
-
}
|
|
55
|
-
getFinishedSpans() {
|
|
56
|
-
return this._spans;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function removeCircular(obj) {
|
|
61
|
-
const seen = new WeakMap(); // Used to keep track of visited objects
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// Replacer function to handle circular references
|
|
65
|
-
function replacer(key, value) {
|
|
66
|
-
if (key === "_spanProcessor") {
|
|
67
|
-
return "oas-telemetry skips this field to avoid circular reference";
|
|
68
|
-
}
|
|
69
|
-
// GENERIC CIRCULAR REFERENCE HANDLING
|
|
70
|
-
// if (typeof value === "object" && value !== null) {
|
|
71
|
-
// // If the object has been visited before, return the name prefixed with "CIRCULAR+"
|
|
72
|
-
// if (seen.has(value)) {
|
|
73
|
-
// return `CIRCULAR${key}`;
|
|
74
|
-
// }
|
|
75
|
-
// seen.set(value, key); // Mark the object as visited with its name
|
|
76
|
-
// }
|
|
77
|
-
return value;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Convert the object to a string and then parse it back
|
|
81
|
-
// This will trigger the replacer function to handle circular references
|
|
82
|
-
const jsonString = JSON.stringify(obj, replacer);
|
|
83
|
-
const spanNoDotsInKeys =jsonString.replace(/[^"]*":/g, (match) => {
|
|
84
|
-
// Replace all dots in the key with underscores (e.g. "http.method" -> "http_method")
|
|
85
|
-
const newMatch = match.replace(/\./g,"_dot_")
|
|
86
|
-
|
|
87
|
-
return newMatch
|
|
88
|
-
})
|
|
89
|
-
return JSON.parse(spanNoDotsInKeys);
|
|
1
|
+
import { ExportResultCode } from '@opentelemetry/core';
|
|
2
|
+
|
|
3
|
+
//import in memory database
|
|
4
|
+
import dataStore from 'nedb'
|
|
5
|
+
|
|
6
|
+
export class InMemoryExporter {
|
|
7
|
+
constructor() {
|
|
8
|
+
this._spans = new dataStore();
|
|
9
|
+
this._stopped = false;
|
|
10
|
+
}
|
|
11
|
+
export(readableSpans, resultCallback) {
|
|
12
|
+
try {
|
|
13
|
+
if (!this._stopped) {
|
|
14
|
+
// Remove circular references
|
|
15
|
+
const cleanSpans = readableSpans.map(span => removeCircular(span));
|
|
16
|
+
|
|
17
|
+
// Insert spans into the in-memory database
|
|
18
|
+
this._spans.insert(cleanSpans, (err, newDoc) => {
|
|
19
|
+
if (err) {
|
|
20
|
+
console.error(err);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
setTimeout(() => resultCallback({ code: ExportResultCode.SUCCESS }), 0);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Error exporting spans\n' + error.message + '\n' + error.stack);
|
|
29
|
+
return resultCallback({
|
|
30
|
+
code: ExportResultCode.FAILED,
|
|
31
|
+
error: new Error('Error exporting spans\n' + error.message + '\n' + error.stack),
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
start() {
|
|
36
|
+
this._stopped = false;
|
|
37
|
+
}
|
|
38
|
+
stop() {
|
|
39
|
+
this._stopped = true;
|
|
40
|
+
}
|
|
41
|
+
shutdown() {
|
|
42
|
+
this._stopped = true;
|
|
43
|
+
this._spans = new dataStore();
|
|
44
|
+
return this.forceFlush();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Exports any pending spans in the exporter
|
|
48
|
+
*/
|
|
49
|
+
forceFlush() {
|
|
50
|
+
return Promise.resolve();
|
|
51
|
+
}
|
|
52
|
+
reset() {
|
|
53
|
+
this._spans = new dataStore();
|
|
54
|
+
}
|
|
55
|
+
getFinishedSpans() {
|
|
56
|
+
return this._spans;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function removeCircular(obj) {
|
|
61
|
+
const seen = new WeakMap(); // Used to keep track of visited objects
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
// Replacer function to handle circular references
|
|
65
|
+
function replacer(key, value) {
|
|
66
|
+
if (key === "_spanProcessor") {
|
|
67
|
+
return "oas-telemetry skips this field to avoid circular reference";
|
|
68
|
+
}
|
|
69
|
+
// GENERIC CIRCULAR REFERENCE HANDLING
|
|
70
|
+
// if (typeof value === "object" && value !== null) {
|
|
71
|
+
// // If the object has been visited before, return the name prefixed with "CIRCULAR+"
|
|
72
|
+
// if (seen.has(value)) {
|
|
73
|
+
// return `CIRCULAR${key}`;
|
|
74
|
+
// }
|
|
75
|
+
// seen.set(value, key); // Mark the object as visited with its name
|
|
76
|
+
// }
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Convert the object to a string and then parse it back
|
|
81
|
+
// This will trigger the replacer function to handle circular references
|
|
82
|
+
const jsonString = JSON.stringify(obj, replacer);
|
|
83
|
+
const spanNoDotsInKeys =jsonString.replace(/[^"]*":/g, (match) => {
|
|
84
|
+
// Replace all dots in the key with underscores (e.g. "http.method" -> "http_method")
|
|
85
|
+
const newMatch = match.replace(/\./g,"_dot_")
|
|
86
|
+
|
|
87
|
+
return newMatch
|
|
88
|
+
})
|
|
89
|
+
return JSON.parse(spanNoDotsInKeys);
|
|
90
90
|
}
|