@oas-tools/oas-telemetry 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -1,255 +1,310 @@
1
- // telemetryMiddleware.js
2
- import { inMemoryExporter } from './telemetry.js';
3
- import { Router,json } from 'express';
4
- import v8 from 'v8';
5
- import { readFileSync, existsSync } from 'fs';
6
- import path from 'path';
7
- import yaml from 'js-yaml';
8
- import ui from './ui.js'
9
- import axios from 'axios';
10
-
11
- let dbglog = ()=>{};
12
-
13
- if(process.env.OTDEBUG == "true")
14
- dbglog = console.log;
15
-
16
- let plugins = [];
17
-
18
- let telemetryStatus = {
19
- active : true
20
- };
21
-
22
- let baseURL = '/telemetry';
23
-
24
- let telemetryConfig = {
25
- exporter: inMemoryExporter,
26
- specFileName: ""
27
- };
28
-
29
- export default function oasTelemetry(tlConfig) {
30
- if (tlConfig) {
31
- dbglog('Telemetry config provided');
32
- telemetryConfig = tlConfig;
33
- if (telemetryConfig.exporter == undefined)
34
- telemetryConfig.exporter = inMemoryExporter;
35
- }
36
-
37
- if (telemetryConfig.spec)
38
- dbglog(`Spec content provided`);
39
- else {
40
- if (telemetryConfig.specFileName != "")
41
- dbglog(`Spec file used for telemetry: ${telemetryConfig.specFileName}`);
42
- else {
43
- console.error("No spec available !");
44
- }
45
- }
46
-
47
- const router = Router();
48
-
49
- if (telemetryConfig.baseURL)
50
- baseURL = telemetryConfig.baseURL;
51
-
52
- router.use(json());
53
-
54
- router.get(baseURL, mainPage);
55
- router.get(baseURL+"/detail/*", detailPage);
56
- router.get(baseURL+"/spec", specLoader);
57
- router.get(baseURL+"/api", apiPage);
58
- router.get(baseURL+"/start", startTelemetry);
59
- router.get(baseURL+"/stop", stopTelemetry);
60
- router.get(baseURL+"/status", statusTelemetry);
61
- router.get(baseURL+"/reset", resetTelemetry);
62
- router.get(baseURL+"/list", listTelemetry);
63
- router.post(baseURL+"/find", findTelemetry);
64
- router.get(baseURL+"/heapStats", heapStats);
65
- router.get(baseURL+"/plugins", listPlugins);
66
- router.post(baseURL+"/plugins", registerPlugin);
67
- return router;
68
- }
69
-
70
- const apiPage = (req, res) => {
71
- let text = `
72
- <h1>Telemetry API routes:</h1>
73
- <ul>
74
- <li><a href="/telemetry/start">/telemetry/start</a></li>
75
- <li><a href="/telemetry/stop">/telemetry/stop</a></li>
76
- <li><a href="/telemetry/status">/telemetry/status</a></li>
77
- <li><a href="/telemetry/reset">/telemetry/reset</a></li>
78
- <li><a href="/telemetry/list">/telemetry/list</a></li>
79
- <li><a href="/telemetry/heapStats">/telemetry/heapStats</a></li>
80
- <li>/telemetry/find [POST]</li>
81
- </ul>
82
- `;
83
- res.send(text);
84
- }
85
-
86
- const mainPage = (req, res) => {
87
- res.set('Content-Type', 'text/html');
88
- res.send(ui().main);
89
- }
90
- const detailPage = (req, res) => {
91
- res.set('Content-Type', 'text/html');
92
- res.send(ui().detail);
93
- }
94
-
95
- const specLoader = (req, res) => {
96
- if (telemetryConfig.specFileName) {
97
- try {
98
- const data = readFileSync(telemetryConfig.specFileName,
99
- { encoding: 'utf8', flag: 'r' });
100
-
101
- const extension = path.extname(telemetryConfig.specFileName);
102
-
103
- let json = data;
104
-
105
- if (extension == yaml)
106
- json = JSON.stringify(yaml.SafeLoad(data), null, 2);
107
-
108
- res.setHeader('Content-Type', 'application/json');
109
- res.send(json);
110
-
111
- } catch (e) {
112
- console.error(`ERROR loading spec file ${telemetryConfig.specFileName}: ${e}`)
113
- }
114
- } else {
115
- if (telemetryConfig.spec) {
116
- let spec = false;
117
-
118
- try {
119
- spec = JSON.parse(telemetryConfig.spec);
120
- } catch (ej) {
121
- try {
122
- spec = JSON.stringify(yaml.load(telemetryConfig.spec), null, 2);
123
- } catch (ey) {
124
- console.error(`Error parsing spec: ${ej} - ${ey}`);
125
- }
126
- }
127
-
128
- if (!spec) {
129
- res.status(404);
130
- } else {
131
- res.setHeader('Content-Type', 'application/json');
132
- res.send(spec);
133
- }
134
-
135
- }
136
- }
137
- }
138
-
139
-
140
- const startTelemetry = (req, res) => {
141
- telemetryConfig.exporter.start();
142
- res.send('Telemetry started');
143
- }
144
- const stopTelemetry = (req, res) => {
145
- telemetryConfig.exporter.stop();
146
-
147
- res.send('Telemetry stopped');
148
- }
149
- const statusTelemetry = (req, res) => {
150
- const status = !telemetryConfig.exporter._stopped || false;
151
- res.send({ active: status });
152
- }
153
-
154
-
155
- const resetTelemetry = (req, res) => {
156
- telemetryConfig.exporter.reset();
157
- res.send('Telemetry reset');
158
- }
159
- const listTelemetry = (req, res) => {
160
- const spansDB = telemetryConfig.exporter.getFinishedSpans();
161
- spansDB.find({}, (err, docs) => {
162
- if (err) {
163
- console.error(err);
164
- return;
165
- }
166
- const spans = docs;
167
- res.send({ spansCount: spans.length, spans: spans });
168
- });
169
- }
170
-
171
- const heapStats = (req, res) => {
172
- var heapStats = v8.getHeapStatistics();
173
-
174
- // Round stats to MB
175
- var roundedHeapStats = Object.getOwnPropertyNames(heapStats).reduce(function (map, stat) {
176
- map[stat] = Math.round((heapStats[stat] / 1024 / 1024) * 1000) / 1000;
177
- return map;
178
- }, {});
179
- roundedHeapStats['units'] = 'MB';
180
-
181
- res.send(roundedHeapStats);
182
- }
183
-
184
- const findTelemetry = (req, res) => {
185
- const spansDB = telemetryConfig.exporter.getFinishedSpans();
186
- const body = req.body;
187
- const search = body?.search ? body.search : {};
188
- if (body?.flags?.containsRegex) {
189
- try {
190
- body.config?.regexIds?.forEach(regexId => {
191
- search[regexId] = new RegExp(search[regexId]);
192
- });
193
- } catch (e) {
194
- console.error(e);
195
- res.status(404).send({ spansCount: 0, spans: [], error: e });
196
- return;
197
- }
198
- spansDB.find(search, (err, docs) => {
199
- if (err) {
200
- console.error(err);
201
- res.status(404).send({ spansCount: 0, spans: [], error: err });
202
- return;
203
- }
204
-
205
- const spans = docs;
206
- res.send({ spansCount: spans.length, spans: spans });
207
- });
208
- }
209
- }
210
-
211
- const listPlugins = (req, res) => {
212
- res.send(plugins.map((p)=>{
213
- return {
214
- id:p.id,
215
- url:p.url,
216
- active:p.active
217
- };
218
- }));
219
- }
220
-
221
- const registerPlugin = (req, res) => {
222
- let pluginResource = req.body;
223
- dbglog(`Plugin Registration Request: = ${JSON.stringify(req.body,null,2)}...`);
224
- dbglog(`Getting plugin at ${pluginResource.url}...`);
225
-
226
- axios
227
- .get(pluginResource.url)
228
- .then((response) => {
229
- dbglog(`Plugin fetched.`);
230
- const pluginCode = response.data;
231
- dbglog("Plugin size: "+pluginCode.length);
232
- var plugin;
233
- eval(pluginCode);
234
- plugin.load(pluginResource.config);
235
- if(plugin.isConfigured()){
236
- dbglog(`Loaded plugin <${plugin.getName()}>`);
237
- pluginResource.plugin = plugin;
238
- pluginResource.name = plugin.getName();
239
-
240
- pluginResource.active = true;
241
- plugins.push(pluginResource);
242
- inMemoryExporter.activatePlugin(pluginResource.plugin);
243
- res.status(201).send(`Plugin registered`);
244
- }else{
245
- console.error(`Plugin <${plugin.getName()}> can not be configured`);
246
- res.status(400).send(`Plugin configuration problem`);
247
- }
248
-
249
- }).catch((err) => console.error("registerPlugin:"+err));
250
-
251
-
252
- }
253
-
254
-
255
-
1
+ // telemetryMiddleware.js
2
+ import { inMemoryExporter } from './telemetry.js';
3
+ import { Router, json } from 'express';
4
+ import v8 from 'v8';
5
+ import { readFileSync, existsSync } from 'fs';
6
+ import path from 'path';
7
+ import yaml from 'js-yaml';
8
+ import ui from './ui.js'
9
+ import axios from 'axios';
10
+ import { requireFromString, importFromString } from "import-from-string";
11
+ import { installDependencies } from "dynamic-installer"
12
+
13
+ let dbglog = () => { };
14
+
15
+ if (process.env.OTDEBUG == "true")
16
+ dbglog = console.log;
17
+
18
+ let plugins = [];
19
+
20
+ let telemetryStatus = {
21
+ active: true
22
+ };
23
+
24
+ let baseURL = '/telemetry';
25
+
26
+ let telemetryConfig = {
27
+ exporter: inMemoryExporter,
28
+ specFileName: ""
29
+ };
30
+
31
+ export default function oasTelemetry(tlConfig) {
32
+ if (tlConfig) {
33
+ dbglog('Telemetry config provided');
34
+ telemetryConfig = tlConfig;
35
+ if (telemetryConfig.exporter == undefined)
36
+ telemetryConfig.exporter = inMemoryExporter;
37
+ }
38
+
39
+ if (telemetryConfig.spec)
40
+ dbglog(`Spec content provided`);
41
+ else {
42
+ if (telemetryConfig.specFileName != "")
43
+ dbglog(`Spec file used for telemetry: ${telemetryConfig.specFileName}`);
44
+ else {
45
+ console.error("No spec available !");
46
+ }
47
+ }
48
+
49
+ const router = Router();
50
+
51
+ if (telemetryConfig.baseURL)
52
+ baseURL = telemetryConfig.baseURL;
53
+
54
+ router.use(json());
55
+
56
+ router.get(baseURL, mainPage);
57
+ router.get(baseURL + "/detail/*", detailPage);
58
+ router.get(baseURL + "/spec", specLoader);
59
+ router.get(baseURL + "/api", apiPage);
60
+ router.get(baseURL + "/start", startTelemetry);
61
+ router.get(baseURL + "/stop", stopTelemetry);
62
+ router.get(baseURL + "/status", statusTelemetry);
63
+ router.get(baseURL + "/reset", resetTelemetry);
64
+ router.get(baseURL + "/list", listTelemetry);
65
+ router.post(baseURL + "/find", findTelemetry);
66
+ router.get(baseURL + "/heapStats", heapStats);
67
+ router.get(baseURL + "/plugins", listPlugins);
68
+ router.post(baseURL + "/plugins", registerPlugin);
69
+ return router;
70
+ }
71
+
72
+ const apiPage = (req, res) => {
73
+ let text = `
74
+ <h1>Telemetry API routes:</h1>
75
+ <ul>
76
+ <li><a href="/telemetry/start">/telemetry/start</a></li>
77
+ <li><a href="/telemetry/stop">/telemetry/stop</a></li>
78
+ <li><a href="/telemetry/status">/telemetry/status</a></li>
79
+ <li><a href="/telemetry/reset">/telemetry/reset</a></li>
80
+ <li><a href="/telemetry/list">/telemetry/list</a></li>
81
+ <li><a href="/telemetry/heapStats">/telemetry/heapStats</a></li>
82
+ <li>/telemetry/find [POST]</li>
83
+ </ul>
84
+ `;
85
+ res.send(text);
86
+ }
87
+
88
+ const mainPage = (req, res) => {
89
+ res.set('Content-Type', 'text/html');
90
+ res.send(ui().main);
91
+ }
92
+ const detailPage = (req, res) => {
93
+ res.set('Content-Type', 'text/html');
94
+ res.send(ui().detail);
95
+ }
96
+
97
+ const specLoader = (req, res) => {
98
+ if (telemetryConfig.specFileName) {
99
+ try {
100
+ const data = readFileSync(telemetryConfig.specFileName,
101
+ { encoding: 'utf8', flag: 'r' });
102
+
103
+ const extension = path.extname(telemetryConfig.specFileName);
104
+
105
+ let json = data;
106
+
107
+ if (extension == yaml)
108
+ json = JSON.stringify(yaml.SafeLoad(data), null, 2);
109
+
110
+ res.setHeader('Content-Type', 'application/json');
111
+ res.send(json);
112
+
113
+ } catch (e) {
114
+ console.error(`ERROR loading spec file ${telemetryConfig.specFileName}: ${e}`)
115
+ }
116
+ } else {
117
+ if (telemetryConfig.spec) {
118
+ let spec = false;
119
+
120
+ try {
121
+ spec = JSON.parse(telemetryConfig.spec);
122
+ } catch (ej) {
123
+ try {
124
+ spec = JSON.stringify(yaml.load(telemetryConfig.spec), null, 2);
125
+ } catch (ey) {
126
+ console.error(`Error parsing spec: ${ej} - ${ey}`);
127
+ }
128
+ }
129
+
130
+ if (!spec) {
131
+ res.status(404);
132
+ } else {
133
+ res.setHeader('Content-Type', 'application/json');
134
+ res.send(spec);
135
+ }
136
+
137
+ }
138
+ }
139
+ }
140
+
141
+ const startTelemetry = (req, res) => {
142
+ telemetryConfig.exporter.start();
143
+ res.send('Telemetry started');
144
+ }
145
+ const stopTelemetry = (req, res) => {
146
+ telemetryConfig.exporter.stop();
147
+
148
+ res.send('Telemetry stopped');
149
+ }
150
+ const statusTelemetry = (req, res) => {
151
+ const status = !telemetryConfig.exporter._stopped || false;
152
+ res.send({ active: status });
153
+ }
154
+
155
+ const resetTelemetry = (req, res) => {
156
+ telemetryConfig.exporter.reset();
157
+ res.send('Telemetry reset');
158
+ }
159
+ const listTelemetry = (req, res) => {
160
+ const spansDB = telemetryConfig.exporter.getFinishedSpans();
161
+ spansDB.find({}, (err, docs) => {
162
+ if (err) {
163
+ console.error(err);
164
+ return;
165
+ }
166
+ const spans = docs;
167
+ res.send({ spansCount: spans.length, spans: spans });
168
+ });
169
+ }
170
+
171
+ const heapStats = (req, res) => {
172
+ var heapStats = v8.getHeapStatistics();
173
+
174
+ // Round stats to MB
175
+ var roundedHeapStats = Object.getOwnPropertyNames(heapStats).reduce(function (map, stat) {
176
+ map[stat] = Math.round((heapStats[stat] / 1024 / 1024) * 1000) / 1000;
177
+ return map;
178
+ }, {});
179
+ roundedHeapStats['units'] = 'MB';
180
+
181
+ res.send(roundedHeapStats);
182
+ }
183
+
184
+ const findTelemetry = (req, res) => {
185
+ const spansDB = telemetryConfig.exporter.getFinishedSpans();
186
+ const body = req.body;
187
+ const search = body?.search ? body.search : {};
188
+ if (body?.flags?.containsRegex) {
189
+ try {
190
+ body.config?.regexIds?.forEach(regexId => {
191
+ search[regexId] = new RegExp(search[regexId]);
192
+ });
193
+ } catch (e) {
194
+ console.error(e);
195
+ res.status(404).send({ spansCount: 0, spans: [], error: e });
196
+ return;
197
+ }
198
+ spansDB.find(search, (err, docs) => {
199
+ if (err) {
200
+ console.error(err);
201
+ res.status(404).send({ spansCount: 0, spans: [], error: err });
202
+ return;
203
+ }
204
+
205
+ const spans = docs;
206
+ res.send({ spansCount: spans.length, spans: spans });
207
+ });
208
+ }
209
+ }
210
+
211
+ const listPlugins = (req, res) => {
212
+ res.send(plugins.map((p) => {
213
+ return {
214
+ id: p.id,
215
+ url: p.url,
216
+ active: p.active
217
+ };
218
+ }));
219
+ }
220
+
221
+ const registerPlugin = async (req, res) => {
222
+ let pluginResource = req.body;
223
+ dbglog(`Plugin Registration Request: = ${JSON.stringify(req.body, null, 2)}...`);
224
+ dbglog(`Getting plugin at ${pluginResource.url}...`);
225
+ let pluginCode;
226
+ if (!pluginResource.url && !pluginResource.code) {
227
+ res.status(400).send(`Plugin code or URL must be provided`);
228
+ return;
229
+ }
230
+
231
+ let module;
232
+ try {
233
+ if (pluginResource.code) {
234
+ pluginCode = pluginResource.code
235
+ } else {
236
+ const response = await axios.get(pluginResource.url);
237
+ pluginCode = response.data;
238
+ }
239
+ if (!pluginCode) {
240
+ res.status(400).send(`Plugin code could not be loaded`);
241
+ return;
242
+ }
243
+ //install dependencies if any
244
+ if (pluginResource.install) {
245
+ const dependenciesStatus = await installDependencies(pluginResource.install);
246
+ if (!dependenciesStatus.success) {
247
+ if (pluginResource.install.ignoreErrors === true) {
248
+ console.warn(`Warning: Error installing dependencies: ${JSON.stringify(dependenciesStatus.details)}`);
249
+ } else {
250
+ res.status(400).send(`Error installing dependencies: ${JSON.stringify(dependenciesStatus.details)}`);
251
+ return;
252
+ }
253
+ }
254
+ }
255
+
256
+ dbglog("Plugin size: " + pluginCode?.length);
257
+ dbglog("Plugin format: " + pluginResource?.moduleFormat);
258
+ if (pluginResource?.moduleFormat && pluginResource.moduleFormat.toUpperCase() == "ESM") {
259
+ console.log("ESM detected")
260
+ module = await importFromString(pluginCode)
261
+ } else {
262
+ console.log("CJS detected (default)")
263
+ module = await requireFromString(pluginCode)
264
+ console.log(module)
265
+ }
266
+ } catch (error) {
267
+ console.error(`Error loading plugin: ${error}`);
268
+ res.status(400).send(`Error loading plugin: ${error}`);
269
+ return;
270
+ }
271
+
272
+ // Check if the plugin is in module.default or module.plugin (supports default syntax)
273
+ const plugin = module.default?.plugin || module.plugin;
274
+
275
+ if (plugin == undefined) {
276
+ res.status(400).send(`Plugin code should export a "plugin" object`);
277
+ console.log("Error in plugin code: no plugin object exported");
278
+ return;
279
+ }
280
+ for (let requiredFunction of ["load", "getName", "isConfigured"]) {
281
+ if (plugin[requiredFunction] == undefined) {
282
+ res.status(400).send(`The plugin code exports a "plugin" object, however it should have a "${requiredFunction}" method`);
283
+ console.log("Error in plugin code: some required functions are missing");
284
+ return;
285
+ }
286
+ }
287
+
288
+ try {
289
+ await plugin.load(pluginResource.config);
290
+ } catch (error) {
291
+ console.error(`Error loading plugin configuration: ${error}`);
292
+ res.status(400).send(`Error loading plugin configuration: ${error}`);
293
+ return;
294
+ }
295
+ if (plugin.isConfigured()) {
296
+ dbglog(`Loaded plugin <${plugin.getName()}>`);
297
+ pluginResource.plugin = plugin;
298
+ pluginResource.name = plugin.getName();
299
+ pluginResource.active = true;
300
+ plugins.push(pluginResource);
301
+ inMemoryExporter.activatePlugin(pluginResource.plugin);
302
+ res.status(201).send(`Plugin registered`);
303
+ } else {
304
+ console.error(`Plugin <${plugin.getName()}> can not be configured`);
305
+ res.status(400).send(`Plugin configuration problem`);
306
+ }
307
+
308
+
309
+ }
310
+
package/src/telemetry.js CHANGED
@@ -1,25 +1,25 @@
1
-
2
-
3
- // tracing.js
4
-
5
- 'use strict'
6
-
7
- import { NodeSDK } from '@opentelemetry/sdk-node';
8
- // import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
9
- import { Resource } from '@opentelemetry/resources';
10
- import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
11
- import { InMemoryExporter } from './exporters/InMemoryDbExporter.js';
12
-
13
-
14
- // Create an in-memory span exporter
15
- export const inMemoryExporter = new InMemoryExporter();
16
-
17
- const traceExporter = inMemoryExporter;
18
- const sdk = new NodeSDK({
19
- resource: new Resource(),
20
- traceExporter,
21
- instrumentations: [new HttpInstrumentation()]
22
- });
23
-
24
- sdk.start()
25
-
1
+
2
+
3
+ // tracing.js
4
+
5
+ 'use strict'
6
+
7
+ import { NodeSDK } from '@opentelemetry/sdk-node';
8
+ // import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
9
+ import { Resource } from '@opentelemetry/resources';
10
+ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
11
+ import { InMemoryExporter } from './exporters/InMemoryDbExporter.js';
12
+
13
+
14
+ // Create an in-memory span exporter
15
+ export const inMemoryExporter = new InMemoryExporter();
16
+
17
+ const traceExporter = inMemoryExporter;
18
+ const sdk = new NodeSDK({
19
+ resource: new Resource(),
20
+ traceExporter,
21
+ instrumentations: [new HttpInstrumentation()]
22
+ });
23
+
24
+ sdk.start()
25
+