@oas-tools/oas-telemetry 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +151 -133
  3. package/dist/client.cjs +14 -0
  4. package/dist/config.cjs +27 -0
  5. package/dist/controllers/pluginController.cjs +118 -0
  6. package/dist/controllers/telemetryController.cjs +92 -0
  7. package/dist/controllers/uiController.cjs +78 -0
  8. package/dist/exporters/InMemoryDbExporter.cjs +45 -42
  9. package/dist/exporters/consoleExporter.cjs +52 -0
  10. package/dist/exporters/dynamicExporter.cjs +64 -0
  11. package/dist/index.cjs +61 -248
  12. package/dist/middleware/auth.cjs +17 -0
  13. package/dist/middleware/authMiddleware.cjs +19 -0
  14. package/dist/openTelemetry.cjs +20 -0
  15. package/dist/routes/authRoutes.cjs +79 -0
  16. package/dist/routes/telemetryRoutes.cjs +31 -0
  17. package/{src/ui.js → dist/services/uiService.cjs} +1140 -813
  18. package/dist/telemetry.cjs +0 -0
  19. package/dist/types/exporters/InMemoryDbExporter.d.ts +16 -0
  20. package/dist/types/index.d.ts +1 -0
  21. package/dist/types/telemetry.d.ts +2 -0
  22. package/dist/types/ui.d.ts +4 -0
  23. package/dist/ui.cjs +0 -0
  24. package/package.json +75 -71
  25. package/src/config.js +19 -0
  26. package/src/controllers/pluginController.js +115 -0
  27. package/src/controllers/telemetryController.js +68 -0
  28. package/src/controllers/uiController.js +69 -0
  29. package/src/dev/ui/login.html +32 -0
  30. package/src/exporters/InMemoryDbExporter.js +180 -175
  31. package/src/exporters/consoleExporter.js +47 -0
  32. package/src/exporters/dynamicExporter.js +62 -0
  33. package/src/index.js +85 -307
  34. package/src/middleware/authMiddleware.js +14 -0
  35. package/src/openTelemetry.js +22 -0
  36. package/src/routes/authRoutes.js +53 -0
  37. package/src/routes/telemetryRoutes.js +38 -0
  38. package/src/services/uiService.js +1520 -0
  39. package/src/telemetry.js +0 -25
File without changes
@@ -0,0 +1,16 @@
1
+ export class InMemoryExporter {
2
+ static plugins: any[];
3
+ _spans: any;
4
+ _stopped: boolean;
5
+ export(readableSpans: any, resultCallback: any): any;
6
+ start(): void;
7
+ stop(): void;
8
+ shutdown(): Promise<void>;
9
+ /**
10
+ * Exports any pending spans in the exporter
11
+ */
12
+ forceFlush(): Promise<void>;
13
+ reset(): void;
14
+ getFinishedSpans(): any;
15
+ activatePlugin(plugin: any): void;
16
+ }
@@ -0,0 +1 @@
1
+ export default function oasTelemetry(tlConfig?: any): import("express-serve-static-core").Router;
@@ -0,0 +1,2 @@
1
+ export const inMemoryExporter: InMemoryExporter;
2
+ import { InMemoryExporter } from './exporters/InMemoryDbExporter.js';
@@ -0,0 +1,4 @@
1
+ export default function ui(): {
2
+ main: string;
3
+ detail: string;
4
+ };
package/dist/ui.cjs CHANGED
File without changes
package/package.json CHANGED
@@ -1,71 +1,75 @@
1
- {
2
- "name": "@oas-tools/oas-telemetry",
3
- "version": "0.3.0",
4
- "description": "This package exports an Express middleware that traces requests and responses of an Express application using OpenTelemetry.",
5
- "author": "Manuel Otero",
6
- "contributors": [
7
- "Alejandro Santisteban",
8
- "Pablo Fernandez"
9
- ],
10
- "license": "Apache-2.0",
11
- "type": "module",
12
- "scripts": {
13
- "devImportUi": "node dev/ui/importUiToHtml.js",
14
- "devExportUi": "node dev/ui/exportHtmlToUi.js",
15
- "build": "babel src -d dist --out-file-extension .cjs",
16
- "devUi": "nodemon --watch dev/ui --ext html --exec \"npm run devExportUi && npm run build && node test/functional/otTestServer.cjs\"",
17
- "publish": "npm run build && npm publish",
18
- "testPerformance": "npm i && cd test/performance/ && ./test.sh",
19
- "pretest": "npm run build",
20
- "test": "npm run testCJS && npm run testMJS",
21
- "testCJS": "npm run preTestCJS && npm run launchTestCJS && npm run postTestCJS",
22
- "preTestCJS": "node test/functional/otTestServer.cjs &",
23
- "launchTestCJS": "npx -y wait-on http://localhost:3000/api/v1/pets && npm run sendRequests && sleep 4 && npm run checkRequestsLog",
24
- "postTestCJS": "kill `ps -uax | grep \"node test/functional/otTestServer\" | grep -v \"grep\" | grep -v \"sh\" | awk '{print $2}'`",
25
- "testMJS": "npm run preTestMJS && npm run launchTestMJS && npm run postTestMJS",
26
- "preTestMJS": "node test/functional/otTestServer.mjs &",
27
- "launchTestMJS": "npx -y wait-on http://localhost:3000/api/v1/pets && npm run sendRequests && sleep 4 && npm run checkRequestsLog",
28
- "postTestMJS": "kill `ps -uax | grep \"node test/functional/otTestServer\" | grep -v \"grep\" | grep -v \"sh\" | awk '{print $2}'`",
29
- "sendRequests": "npx -y newman run test/functional/request-collection.json -e test/functional/request-environment.json",
30
- "checkRequestsLog": "npx -y newman run test/functional/request-collection-check.json -e test/functional/request-environment.json",
31
- "launchKSApi": "npm run build; (cd test/performance/ks-api ; node indexTelemetry.js)",
32
- "launchDevel": "export OTDEBUG=true && npm run launchKSApi"
33
- },
34
- "files": [
35
- "dist",
36
- "src"
37
- ],
38
- "main": "./dist/index.cjs",
39
- "module": "./src/index.js",
40
- "exports": {
41
- "require": "./dist/index.cjs",
42
- "import": "./src/index.js",
43
- "default": "./src/index.js"
44
- },
45
- "dependencies": {
46
- "@opentelemetry/instrumentation-http": "^0.51.0",
47
- "@opentelemetry/resources": "^1.24.0",
48
- "@opentelemetry/sdk-node": "^0.49.1",
49
- "axios": "^1.6.8",
50
- "dynamic-installer": "^1.0.1",
51
- "express": "^4.19.2",
52
- "import-from-string": "^0.0.4",
53
- "js-yaml": "^4.1.0",
54
- "nedb": "^1.8.0",
55
- "readline": "^1.3.0",
56
- "v8": "^0.1.0"
57
- },
58
- "devDependencies": {
59
- "@babel/cli": "^7.24.1",
60
- "@babel/core": "^7.24.4",
61
- "@babel/plugin-proposal-class-properties": "^7.18.6",
62
- "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
63
- "@babel/preset-env": "^7.24.4",
64
- "@opentelemetry/api": "^1.8.0",
65
- "@opentelemetry/auto-instrumentations-node": "^0.43.0",
66
- "apipecker": "^1.3.1",
67
- "babel-plugin-add-module-exports": "^1.0.4",
68
- "babel-plugin-module-extension": "^0.1.3",
69
- "nodemon": "^3.1.0"
70
- }
71
- }
1
+ {
2
+ "name": "@oas-tools/oas-telemetry",
3
+ "version": "0.5.0",
4
+ "description": "This package exports an Express middleware that traces requests and responses of an Express application using OpenTelemetry.",
5
+ "author": "Manuel Otero",
6
+ "contributors": [
7
+ "Alejandro Santisteban",
8
+ "Pablo Fernandez"
9
+ ],
10
+ "license": "Apache-2.0",
11
+ "type": "module",
12
+ "scripts": {
13
+ "devImportUi": "node dev/ui/importUiToHtml.js",
14
+ "devExportUi": "node dev/ui/exportHtmlToUi.js",
15
+ "build": "babel src -d dist --out-file-extension .cjs",
16
+ "devUi": "nodemon --watch dev/ui src --ext html --exec \"npm run devExportUi && npm run build && node test/functional/otTestServer.cjs\"",
17
+ "devUiEsm": "nodemon --watch dev/ui --ext html --exec \"npm run devExportUi && node test/functional/otTestServer.mjs\"",
18
+ "publish": "npm run build && npm publish",
19
+ "testPerformance": "npm i && cd test/performance/ && ./test.sh",
20
+ "pretest": "npm run build",
21
+ "test": "npm run testCJS && npm run testMJS",
22
+ "testCJS": "npm run preTestCJS && npm run launchTestCJS && npm run postTestCJS",
23
+ "preTestCJS": "node test/functional/otTestServer.cjs &",
24
+ "launchTestCJS": "npx -y wait-on http://localhost:3000/api/v1/pets && npm run sendRequests && sleep 4 && npm run checkRequestsLog",
25
+ "postTestCJS": "kill `ps -uax | grep \"node test/functional/otTestServer\" | grep -v \"grep\" | grep -v \"sh\" | awk '{print $2}'`",
26
+ "testMJS": "npm run preTestMJS && npm run launchTestMJS && npm run postTestMJS",
27
+ "preTestMJS": "node test/functional/otTestServer.mjs &",
28
+ "launchTestMJS": "npx -y wait-on http://localhost:3000/api/v1/pets && npm run sendRequests && sleep 4 && npm run checkRequestsLog",
29
+ "postTestMJS": "kill `ps -uax | grep \"node test/functional/otTestServer\" | grep -v \"grep\" | grep -v \"sh\" | awk '{print $2}'`",
30
+ "sendRequests": "npx -y newman run test/functional/request-collection.json -e test/functional/request-environment.json",
31
+ "checkRequestsLog": "npx -y newman run test/functional/request-collection-check.json -e test/functional/request-environment.json",
32
+ "launchKSApi": "npm run build; (cd test/performance/ks-api ; node indexTelemetry.js)",
33
+ "launchDevel": "export OTDEBUG=true && npm run launchKSApi"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "src"
38
+ ],
39
+ "main": "./dist/index.cjs",
40
+ "module": "./src/index.js",
41
+ "exports": {
42
+ "require": "./dist/index.cjs",
43
+ "import": "./src/index.js",
44
+ "default": "./src/index.js"
45
+ },
46
+ "dependencies": {
47
+ "@opentelemetry/instrumentation-http": "^0.51.0",
48
+ "@opentelemetry/resources": "^1.24.0",
49
+ "@opentelemetry/sdk-node": "^0.49.1",
50
+ "axios": "^1.6.8",
51
+ "cookie-parser": "^1.4.7",
52
+ "dynamic-installer": "^1.1.1",
53
+ "express": "^4.19.2",
54
+ "flatted": "^3.3.2",
55
+ "import-from-string": "^0.0.4",
56
+ "js-yaml": "^4.1.0",
57
+ "jsonwebtoken": "^9.0.2",
58
+ "nedb": "^1.8.0",
59
+ "readline": "^1.3.0",
60
+ "v8": "^0.1.0"
61
+ },
62
+ "devDependencies": {
63
+ "@babel/cli": "^7.24.1",
64
+ "@babel/core": "^7.24.4",
65
+ "@babel/plugin-proposal-class-properties": "^7.18.6",
66
+ "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
67
+ "@babel/preset-env": "^7.24.4",
68
+ "@opentelemetry/api": "^1.8.0",
69
+ "@opentelemetry/auto-instrumentations-node": "^0.43.0",
70
+ "apipecker": "^1.3.1",
71
+ "babel-plugin-add-module-exports": "^1.0.4",
72
+ "babel-plugin-module-extension": "^0.1.3",
73
+ "nodemon": "^3.1.0"
74
+ }
75
+ }
package/src/config.js ADDED
@@ -0,0 +1,19 @@
1
+ import DynamicExporter from "./exporters/dynamicExporter.js";
2
+
3
+ //Environment variables
4
+ //OASTLM_API_KEY = 'oas-telemetry-api-key' //Cookie value for the API key
5
+ //OASTLM_MODULE_DISABLED = 'true' //Disables the module (empty middleware and no tracing)
6
+
7
+ export const globalOasTlmConfig = {
8
+ dynamicExporter: new DynamicExporter(),
9
+ baseURL: "/telemetry4",
10
+ spec: null,
11
+ specFileName: "",
12
+ autoActivate: true,
13
+ apiKeyMaxAge: 1000 * 60 * 60, // 1 hour
14
+ password: "oas-telemetry-password",
15
+ jwtSecret: "oas-telemetry-secret",
16
+ };
17
+ //5 name alternatives. one for globals
18
+
19
+ export default {globalOasTlmConfig}
@@ -0,0 +1,115 @@
1
+
2
+ import { globalOasTlmConfig } from '../config.js';
3
+ import axios from 'axios';
4
+ import { importFromString, requireFromString } from 'import-from-string';
5
+ import { installDependencies } from 'dynamic-installer';
6
+
7
+ let dbglog = () => { };
8
+
9
+ if (process.env.OTDEBUG == "true")
10
+ dbglog = console.log;
11
+ export const listPlugins = (req, res) => {
12
+ res.send(globalOasTlmConfig.dynamicExporter.getPlugins().map((p) => {
13
+ return {
14
+ id: p.id,
15
+ name: p.name,
16
+ url: p.url,
17
+ active: p.active
18
+ };
19
+ }));
20
+ }
21
+
22
+ export const registerPlugin = async (req, res) => {
23
+ let pluginResource = req.body;
24
+ dbglog(`Plugin Registration Request: = ${JSON.stringify(req.body, null, 2)}...`);
25
+ dbglog(`Getting plugin at ${pluginResource.url}...`);
26
+ let pluginCode;
27
+ if (!pluginResource.url && !pluginResource.code) {
28
+ res.status(400).send(`Plugin code or URL must be provided`);
29
+ return;
30
+ }
31
+
32
+ let module;
33
+ try {
34
+ if (pluginResource.code) {
35
+ pluginCode = pluginResource.code
36
+ } else {
37
+ const response = await axios.get(pluginResource.url);
38
+ pluginCode = response.data;
39
+ }
40
+ if (!pluginCode) {
41
+ res.status(400).send(`Plugin code could not be loaded`);
42
+ return;
43
+ }
44
+ if (pluginResource.install) {
45
+ const dependenciesStatus = await installDependencies(pluginResource.install);
46
+ if (!dependenciesStatus.success) {
47
+ if (pluginResource.install.ignoreErrors === true) {
48
+ console.warn(`Warning: Error installing dependencies: ${JSON.stringify(dependenciesStatus.details)}`);
49
+ } else {
50
+ res.status(400).send(`Error installing dependencies: ${JSON.stringify(dependenciesStatus.details)}`);
51
+ return;
52
+ }
53
+ }
54
+ }
55
+
56
+ dbglog("Plugin size: " + pluginCode?.length);
57
+ dbglog("Plugin format: " + pluginResource?.moduleFormat);
58
+ if (pluginResource?.moduleFormat && pluginResource.moduleFormat.toUpperCase() == "ESM") {
59
+ console.log("ESM detected")
60
+ module = await importFromString(pluginCode)
61
+ } else {
62
+ console.log("CJS detected (default)")
63
+ module = await requireFromString(pluginCode)
64
+ console.log(module)
65
+ }
66
+ } catch (error) {
67
+ console.error(`Error loading plugin: ${error}`);
68
+ res.status(400).send(`Error loading plugin: ${error}`);
69
+ return;
70
+ }
71
+
72
+ const plugin = module.default?.plugin ?? module.plugin;
73
+
74
+ if (plugin == undefined) {
75
+ res.status(400).send(`Plugin code should export a "plugin" object`);
76
+ console.log("Error in plugin code: no plugin object exported");
77
+ return;
78
+ }
79
+ for (let requiredFunction of ["load", "getName", "isConfigured"]) {
80
+ if (plugin[requiredFunction] == undefined) {
81
+ res.status(400).send(`The plugin code exports a "plugin" object, however it should have a "${requiredFunction}" method`);
82
+ console.log("Error in plugin code: some required functions are missing");
83
+ return;
84
+ }
85
+ }
86
+
87
+ try {
88
+ await plugin.load(pluginResource.config);
89
+ } catch (error) {
90
+ console.error(`Error loading plugin configuration: ${error}`);
91
+ res.status(400).send(`Error loading plugin configuration: ${error}`);
92
+ return;
93
+ }
94
+ if (plugin.isConfigured()) {
95
+ dbglog(`Loaded plugin <${plugin.getName()}>`);
96
+ pluginResource.plugin = plugin;
97
+ pluginResource.name = plugin.getName();
98
+ pluginResource.active = true;
99
+ globalOasTlmConfig.dynamicExporter.pushPlugin(pluginResource);
100
+ globalOasTlmConfig.dynamicExporter.activatePlugin(pluginResource.plugin);
101
+ res.status(201).send(`Plugin registered`);
102
+ } else {
103
+ console.error(`Plugin <${plugin.getName()}> can not be configured`);
104
+ res.status(400).send(`Plugin configuration problem`);
105
+ }
106
+ }
107
+
108
+ export const checkApiKey = (req, res) => {
109
+ const apiKey = req.query.apiKey || req.body.apiKey;
110
+ if (apiKey === process.env.APIKEY) {
111
+ res.status(200).send({ valid: true });
112
+ } else {
113
+ res.status(401).send({ valid: false });
114
+ }
115
+ }
@@ -0,0 +1,68 @@
1
+ import v8 from 'v8';
2
+ import { globalOasTlmConfig } from '../config.js';
3
+
4
+
5
+ export const startTelemetry = (req, res) => {
6
+ globalOasTlmConfig.dynamicExporter.exporter.start();
7
+ res.send('Telemetry started');
8
+ }
9
+
10
+ export const stopTelemetry = (req, res) => {
11
+ globalOasTlmConfig.dynamicExporter.exporter.stop();
12
+ res.send('Telemetry stopped');
13
+ }
14
+
15
+ export const statusTelemetry = (req, res) => {
16
+ const isRunning = globalOasTlmConfig.dynamicExporter.exporter.isRunning() || false;
17
+ res.send({ active: isRunning });
18
+ }
19
+
20
+ export const resetTelemetry = (req, res) => {
21
+ globalOasTlmConfig.dynamicExporter.exporter.reset();
22
+ res.send('Telemetry reset');
23
+ }
24
+
25
+ export const listTelemetry = async (req, res) => {
26
+ try {
27
+ const spans = await globalOasTlmConfig.dynamicExporter.exporter.getFinishedSpans();
28
+ res.send({ spansCount: spans.length, spans: spans });
29
+ } catch (err) {
30
+ console.error(err);
31
+ res.status(500).send({ error: 'Failed to list telemetry data' });
32
+ }
33
+ }
34
+
35
+ export const heapStats = (req, res) => {
36
+ var heapStats = v8.getHeapStatistics();
37
+ var roundedHeapStats = Object.getOwnPropertyNames(heapStats).reduce(function (map, stat) {
38
+ map[stat] = Math.round((heapStats[stat] / 1024 / 1024) * 1000) / 1000;
39
+ return map;
40
+ }, {});
41
+ roundedHeapStats['units'] = 'MB';
42
+ res.send(roundedHeapStats);
43
+ }
44
+
45
+ export const findTelemetry = (req, res) => {
46
+ const body = req.body;
47
+ const search = body?.search ? body.search : {};
48
+ if (body?.flags?.containsRegex) {
49
+ try {
50
+ body.config?.regexIds?.forEach(regexId => {
51
+ if(search[regexId]) search[regexId] = new RegExp(search[regexId]);
52
+ });
53
+ } catch (e) {
54
+ console.error(e);
55
+ res.status(404).send({ spansCount: 0, spans: [], error: e });
56
+ return;
57
+ }
58
+ globalOasTlmConfig.dynamicExporter.exporter.find(search,(err, docs) => {
59
+ if (err) {
60
+ console.error(err);
61
+ res.status(404).send({ spansCount: 0, spans: [], error: err });
62
+ return;
63
+ }
64
+ const spans = docs;
65
+ res.send({ spansCount: spans.length, spans: spans });
66
+ });
67
+ }
68
+ }
@@ -0,0 +1,69 @@
1
+ import { globalOasTlmConfig } from '../config.js';
2
+ import { readFileSync } from 'fs';
3
+ import path from 'path';
4
+ import yaml from 'js-yaml';
5
+ import ui from '../services/uiService.js';
6
+
7
+ export const apiPage = (req, res) => {
8
+ const baseURL = globalOasTlmConfig.baseURL;
9
+ let text = `
10
+ <h1>Telemetry API routes:</h1>
11
+ <ul>
12
+ <li><a href="${baseURL}/start">${baseURL}/start</a></li>
13
+ <li><a href="${baseURL}/stop">${baseURL}/stop</a></li>
14
+ <li><a href="${baseURL}/status">${baseURL}/status</a></li>
15
+ <li><a href="${baseURL}/reset">${baseURL}/reset</a></li>
16
+ <li><a href="${baseURL}/list">${baseURL}/list</a></li>
17
+ <li><a href="${baseURL}/heapStats">${baseURL}/heapStats</a></li>
18
+ <li>${baseURL}/find [POST]</li>
19
+ </ul>
20
+ `;
21
+ res.send(text);
22
+ }
23
+
24
+ export const mainPage = (req, res) => {
25
+ const baseURL = globalOasTlmConfig.baseURL;
26
+ res.set('Content-Type', 'text/html');
27
+ res.send(ui(baseURL).main);
28
+ }
29
+
30
+ export const detailPage = (req, res) => {
31
+ const baseURL = globalOasTlmConfig.baseURL;
32
+ res.set('Content-Type', 'text/html');
33
+ res.send(ui(baseURL).detail);
34
+ }
35
+
36
+ export const specLoader = (req, res) => {
37
+ if (globalOasTlmConfig.specFileName) {
38
+ try {
39
+ const data = readFileSync(globalOasTlmConfig.specFileName, { encoding: 'utf8', flag: 'r' });
40
+ const extension = path.extname(globalOasTlmConfig.specFileName);
41
+ let json = data;
42
+ if (extension == yaml)
43
+ json = JSON.stringify(yaml.SafeLoad(data), null, 2);
44
+ res.setHeader('Content-Type', 'application/json');
45
+ res.send(json);
46
+ } catch (e) {
47
+ console.error(`ERROR loading spec file ${globalOasTlmConfig.specFileName}: ${e}`);
48
+ }
49
+ } else if (globalOasTlmConfig.spec) {
50
+ let spec = false;
51
+ try {
52
+ spec = JSON.parse(globalOasTlmConfig.spec);
53
+ } catch (ej) {
54
+ try {
55
+ spec = JSON.stringify(yaml.load(globalOasTlmConfig.spec), null, 2);
56
+ } catch (ey) {
57
+ console.error(`Error parsing spec: ${ej} - ${ey}`);
58
+ }
59
+ }
60
+ if (!spec) {
61
+ res.status(404);
62
+ } else {
63
+ res.setHeader('Content-Type', 'application/json');
64
+ res.send(spec);
65
+ }
66
+ } else {
67
+ res.status(404);
68
+ }
69
+ }
@@ -0,0 +1,32 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Login</title>
7
+ </head>
8
+ <body>
9
+ <form id="loginForm">
10
+ <label for="apiKey">API Key:</label>
11
+ <input type="text" id="apiKey" name="apiKey" required>
12
+ <button type="submit">Login</button>
13
+ </form>
14
+
15
+ <script>
16
+ document.getElementById('loginForm').addEventListener('submit', function(event) {
17
+ event.preventDefault();
18
+ const apiKey = document.getElementById('apiKey').value;
19
+ fetch('/login', {
20
+ method: 'POST',
21
+ headers: {
22
+ 'Content-Type': 'application/json'
23
+ },
24
+ body: JSON.stringify({ apiKey })
25
+ })
26
+ .then(response => response.text())
27
+ .then(data => alert(data))
28
+ .catch(error => console.error('Error:', error));
29
+ });
30
+ </script>
31
+ </body>
32
+ </html>