@oas-tools/oas-telemetry 0.2.1 → 0.2.2

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.
@@ -7,6 +7,12 @@ exports.InMemoryExporter = void 0;
7
7
  var _core = require("@opentelemetry/core");
8
8
  var _nedb = _interopRequireDefault(require("nedb"));
9
9
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
11
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
12
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
13
+ let dbglog = () => {};
14
+ if (process.env.OTDEBUG == "true") dbglog = console.log;
15
+
10
16
  //import in memory database
11
17
 
12
18
  class InMemoryExporter {
@@ -24,6 +30,13 @@ class InMemoryExporter {
24
30
 
25
31
  // Insert spans into the in-memory database
26
32
  this._spans.insert(cleanSpans, (err, newDoc) => {
33
+ InMemoryExporter.plugins.forEach((p, i) => {
34
+ cleanSpans.forEach(t => {
35
+ dbglog(`Sending trace <${t._id}> to plugin (Plugin #${i}) <${p.name}>`);
36
+ dbglog(`Trace: \n<${JSON.stringify(t, null, 2)}`);
37
+ p.newTrace(t);
38
+ });
39
+ });
27
40
  if (err) {
28
41
  console.error(err);
29
42
  return;
@@ -64,8 +77,14 @@ class InMemoryExporter {
64
77
  getFinishedSpans() {
65
78
  return this._spans;
66
79
  }
80
+ activatePlugin(plugin) {
81
+ dbglog(`Activating plugin <${plugin.getName()}>...`);
82
+ InMemoryExporter.plugins.push(plugin);
83
+ dbglog(`Plugin <${plugin.getName()}> active (Total active plugins: ${InMemoryExporter.plugins.length})`);
84
+ }
67
85
  }
68
86
  exports.InMemoryExporter = InMemoryExporter;
87
+ _defineProperty(InMemoryExporter, "plugins", []);
69
88
  function removeCircularRefs(obj) {
70
89
  const seen = new WeakMap(); // Used to keep track of visited objects
71
90
 
package/dist/index.cjs CHANGED
@@ -11,9 +11,16 @@ var _fs = require("fs");
11
11
  var _path = _interopRequireDefault(require("path"));
12
12
  var _jsYaml = _interopRequireDefault(require("js-yaml"));
13
13
  var _ui = _interopRequireDefault(require("./ui.cjs"));
14
+ var _axios = _interopRequireDefault(require("axios"));
14
15
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
16
  // telemetryMiddleware.js
16
17
 
18
+ let dbglog = () => {};
19
+ if (process.env.OTDEBUG == "true") dbglog = console.log;
20
+ let plugins = [];
21
+ let telemetryStatus = {
22
+ active: true
23
+ };
17
24
  let baseURL = '/telemetry';
18
25
  let telemetryConfig = {
19
26
  exporter: _telemetry.inMemoryExporter,
@@ -21,19 +28,18 @@ let telemetryConfig = {
21
28
  };
22
29
  function oasTelemetry(tlConfig) {
23
30
  if (tlConfig) {
24
- console.log('Telemetry config provided');
31
+ dbglog('Telemetry config provided');
25
32
  telemetryConfig = tlConfig;
26
33
  if (telemetryConfig.exporter == undefined) telemetryConfig.exporter = _telemetry.inMemoryExporter;
27
34
  }
28
- if (telemetryConfig.spec) console.log(`Spec content provided`);else {
29
- if (telemetryConfig.specFileName != "") console.log(`Spec file used for telemetry: ${telemetryConfig.specFileName}`);else {
30
- console.log("No spec available !");
35
+ if (telemetryConfig.spec) dbglog(`Spec content provided`);else {
36
+ if (telemetryConfig.specFileName != "") dbglog(`Spec file used for telemetry: ${telemetryConfig.specFileName}`);else {
37
+ console.error("No spec available !");
31
38
  }
32
39
  }
33
40
  const router = (0, _express.Router)();
34
-
35
- //const baseURL = telemetryConfig.baseURL;
36
-
41
+ if (telemetryConfig.baseURL) baseURL = telemetryConfig.baseURL;
42
+ router.use((0, _express.json)());
37
43
  router.get(baseURL, mainPage);
38
44
  router.get(baseURL + "/detail/*", detailPage);
39
45
  router.get(baseURL + "/spec", specLoader);
@@ -45,6 +51,8 @@ function oasTelemetry(tlConfig) {
45
51
  router.get(baseURL + "/list", listTelemetry);
46
52
  router.post(baseURL + "/find", findTelemetry);
47
53
  router.get(baseURL + "/heapStats", heapStats);
54
+ router.get(baseURL + "/plugins", listPlugins);
55
+ router.post(baseURL + "/plugins", registerPlugin);
48
56
  return router;
49
57
  }
50
58
  const apiPage = (req, res) => {
@@ -138,6 +146,17 @@ const listTelemetry = (req, res) => {
138
146
  });
139
147
  });
140
148
  };
149
+ const heapStats = (req, res) => {
150
+ var heapStats = _v.default.getHeapStatistics();
151
+
152
+ // Round stats to MB
153
+ var roundedHeapStats = Object.getOwnPropertyNames(heapStats).reduce(function (map, stat) {
154
+ map[stat] = Math.round(heapStats[stat] / 1024 / 1024 * 1000) / 1000;
155
+ return map;
156
+ }, {});
157
+ roundedHeapStats['units'] = 'MB';
158
+ res.send(roundedHeapStats);
159
+ };
141
160
  const findTelemetry = (req, res) => {
142
161
  const spansDB = telemetryConfig.exporter.getFinishedSpans();
143
162
  const body = req.body;
@@ -174,15 +193,38 @@ const findTelemetry = (req, res) => {
174
193
  });
175
194
  }
176
195
  };
177
- const heapStats = (req, res) => {
178
- var heapStats = _v.default.getHeapStatistics();
179
-
180
- // Round stats to MB
181
- var roundedHeapStats = Object.getOwnPropertyNames(heapStats).reduce(function (map, stat) {
182
- map[stat] = Math.round(heapStats[stat] / 1024 / 1024 * 1000) / 1000;
183
- return map;
184
- }, {});
185
- roundedHeapStats['units'] = 'MB';
186
- res.send(roundedHeapStats);
196
+ const listPlugins = (req, res) => {
197
+ res.send(plugins.map(p => {
198
+ return {
199
+ id: p.id,
200
+ url: p.url,
201
+ active: p.active
202
+ };
203
+ }));
204
+ };
205
+ const registerPlugin = (req, res) => {
206
+ let pluginResource = req.body;
207
+ dbglog(`Plugin Registration Request: = ${JSON.stringify(req.body, null, 2)}...`);
208
+ dbglog(`Getting plugin at ${pluginResource.url}...`);
209
+ _axios.default.get(pluginResource.url).then(response => {
210
+ dbglog(`Plugin fetched.`);
211
+ const pluginCode = response.data;
212
+ dbglog("Plugin size: " + pluginCode.length);
213
+ var plugin;
214
+ eval(pluginCode);
215
+ plugin.load(pluginResource.config);
216
+ if (plugin.isConfigured()) {
217
+ dbglog(`Loaded plugin <${plugin.getName()}>`);
218
+ pluginResource.plugin = plugin;
219
+ pluginResource.name = plugin.getName();
220
+ pluginResource.active = true;
221
+ plugins.push(pluginResource);
222
+ _telemetry.inMemoryExporter.activatePlugin(pluginResource.plugin);
223
+ res.status(201).send(`Plugin registered`);
224
+ } else {
225
+ console.error(`Plugin <${plugin.getName()}> can not be configured`);
226
+ res.status(400).send(`Plugin configuration problem`);
227
+ }
228
+ }).catch(err => console.error("registerPlugin:" + err));
187
229
  };
188
230
  module.exports = exports.default;
package/dist/ui.cjs CHANGED
@@ -6,8 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = ui;
7
7
  function ui() {
8
8
  return {
9
- main: `
10
- <!DOCTYPE html>
9
+ main: `<!DOCTYPE html>
11
10
  <html lang="en">
12
11
 
13
12
  <head>
@@ -124,14 +123,16 @@ function ui() {
124
123
  if (c != "") {
125
124
  pathRegExpStr += "/";
126
125
  if (c.charAt(0) == "{" && c.charAt(c.length - 1) == "}") {
127
- pathRegExpStr += "(.*)";
126
+ // Ensure it matches at least one character (.+)
127
+ pathRegExpStr += "(.+)";
128
128
  } else {
129
129
  pathRegExpStr += c;
130
130
  }
131
131
  }
132
132
  });
133
-
134
- pathRegExpStr += "\$";
133
+
134
+ // Allow an optional trailing slash
135
+ pathRegExpStr += "/?\$";
135
136
 
136
137
  return pathRegExpStr;
137
138
  }
@@ -380,8 +381,7 @@ function ui() {
380
381
  </body>
381
382
 
382
383
  </html>`,
383
- detail: `
384
- <!DOCTYPE html>
384
+ detail: `<!DOCTYPE html>
385
385
  <html lang="en">
386
386
 
387
387
  <head>
@@ -584,14 +584,16 @@ function ui() {
584
584
  if (c != "") {
585
585
  pathRegExpStr += "/";
586
586
  if (c.charAt(0) == "{" && c.charAt(c.length - 1) == "}") {
587
- pathRegExpStr += "(.*)";
587
+ // Ensure it matches at least one character (.+)
588
+ pathRegExpStr += "(.+)";
588
589
  } else {
589
590
  pathRegExpStr += c;
590
591
  }
591
592
  }
592
593
  });
593
594
 
594
- pathRegExpStr += "\$";
595
+ // Allow an optional trailing slash
596
+ pathRegExpStr += "/?\$";
595
597
 
596
598
  return pathRegExpStr;
597
599
  }
@@ -600,7 +602,24 @@ function ui() {
600
602
  try {
601
603
  log(\`Fetchig traces for <\${path}> - \${method} - \${status},.. \`);
602
604
 
603
- const response = await fetch("/telemetry/list");
605
+ const body = {
606
+ "flags": { "containsRegex": true },
607
+ "config": { "regexIds": ["attributes.http.target"] },
608
+ "search": {
609
+ "attributes.http.target": getPathRegEx(path),
610
+ "attributes.http.method": method.toUpperCase(),
611
+ "attributes.http.status_code": parseInt(status)
612
+ }
613
+ };
614
+ log("body: " + JSON.stringify(body, null, 2));
615
+ //response is to the post at /telemetry/find
616
+ const response = await fetch("/telemetry/find", {
617
+ method: "POST",
618
+ headers: {
619
+ "Content-Type": "application/json"
620
+ },
621
+ body: JSON.stringify(body)
622
+ });
604
623
 
605
624
  if (!response.ok) {
606
625
  throw new Error("ERROR getting the Traces.");
@@ -608,13 +627,12 @@ function ui() {
608
627
 
609
628
  const responseJSON = await response.json();
610
629
  const traces = responseJSON.spans;
611
- const filteredTraces = traces; //filter made in server side
612
- log(\`Feched \${traces.length} traces.\`);
613
- //log(\`First trace: \${JSON.stringify(traces[0],null,2)}\`);
614
630
 
615
- if (filteredTraces.length != traceCount) {
616
- loadTraces(filteredTraces);
617
- traceCount = filteredTraces.length;
631
+ log(\`Fetched \${traces.length} traces.\`);
632
+
633
+ if (traces.length != traceCount) {
634
+ loadTraces(traces);
635
+ traceCount = traces.length;
618
636
  }
619
637
 
620
638
  setTimeout(fetchTracesByParsing, 1000, path, method, status);
package/package.json CHANGED
@@ -1,29 +1,35 @@
1
1
  {
2
2
  "name": "@oas-tools/oas-telemetry",
3
- "version": "0.2.1",
4
- "description": "This package exports an express midelware that allows to trace the requests and responses of an express application using OpenTelemetry",
3
+ "version": "0.2.2",
4
+ "description": "This package exports an Express middleware that traces requests and responses of an Express application using OpenTelemetry.",
5
5
  "author": "Manuel Otero",
6
6
  "contributors": [
7
- "Alejandro Santisteban","Pablo Fernandez"
7
+ "Alejandro Santisteban",
8
+ "Pablo Fernandez"
8
9
  ],
9
10
  "license": "Apache-2.0",
10
11
  "type": "module",
11
12
  "scripts": {
13
+ "devImportUi": "node dev/ui/importUiToHtml.js",
14
+ "devExportUi": "node dev/ui/exportHtmlToUi.js",
12
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\"",
13
17
  "publish": "npm run build && npm publish",
14
18
  "testPerformance": "npm i && cd test/performance/ && ./test.sh",
15
19
  "pretest": "npm run build",
16
- "test": "npm run testCJS && npm run testMJS",
17
- "testCJS": "npm run preTestCJS && npm run launchTestCJS && npm run postTestCJS",
20
+ "test": "npm run testCJS && npm run testMJS",
21
+ "testCJS": "npm run preTestCJS && npm run launchTestCJS && npm run postTestCJS",
18
22
  "preTestCJS": "node test/functional/otTestServer.cjs &",
19
23
  "launchTestCJS": "npx -y wait-on http://localhost:3000/api/v1/pets && npm run sendRequests && sleep 4 && npm run checkRequestsLog",
20
24
  "postTestCJS": "kill `ps -uax | grep \"node test/functional/otTestServer\" | grep -v \"grep\" | grep -v \"sh\" | awk '{print $2}'`",
21
- "testMJS": "npm run preTestMJS && npm run launchTestMJS && npm run postTestMJS",
25
+ "testMJS": "npm run preTestMJS && npm run launchTestMJS && npm run postTestMJS",
22
26
  "preTestMJS": "node test/functional/otTestServer.mjs &",
23
27
  "launchTestMJS": "npx -y wait-on http://localhost:3000/api/v1/pets && npm run sendRequests && sleep 4 && npm run checkRequestsLog",
24
28
  "postTestMJS": "kill `ps -uax | grep \"node test/functional/otTestServer\" | grep -v \"grep\" | grep -v \"sh\" | awk '{print $2}'`",
25
29
  "sendRequests": "npx -y newman run test/functional/request-collection.json -e test/functional/request-environment.json",
26
- "checkRequestsLog": "npx -y newman run test/functional/request-collection-check.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"
27
33
  },
28
34
  "files": [
29
35
  "dist",
@@ -1,13 +1,21 @@
1
1
  import { ExportResultCode } from '@opentelemetry/core';
2
2
 
3
+ let dbglog = ()=>{};
4
+
5
+ if(process.env.OTDEBUG == "true")
6
+ dbglog = console.log;
7
+
3
8
  //import in memory database
4
- import dataStore from 'nedb'
9
+ import dataStore from 'nedb';
5
10
 
6
11
  export class InMemoryExporter {
7
12
  constructor() {
8
13
  this._spans = new dataStore();
9
14
  this._stopped = true;
10
- }
15
+ };
16
+
17
+ static plugins = [];
18
+
11
19
  export(readableSpans, resultCallback) {
12
20
  try {
13
21
  if (!this._stopped) {
@@ -19,6 +27,13 @@ export class InMemoryExporter {
19
27
 
20
28
  // Insert spans into the in-memory database
21
29
  this._spans.insert(cleanSpans, (err, newDoc) => {
30
+ InMemoryExporter.plugins.forEach((p,i)=>{
31
+ cleanSpans.forEach((t)=>{
32
+ dbglog(`Sending trace <${t._id}> to plugin (Plugin #${i}) <${p.name}>`);
33
+ dbglog(`Trace: \n<${JSON.stringify(t,null,2)}`);
34
+ p.newTrace(t);
35
+ });
36
+ });
22
37
  if (err) {
23
38
  console.error(err);
24
39
  return;
@@ -34,29 +49,34 @@ export class InMemoryExporter {
34
49
  error: new Error('Error exporting spans\n' + error.message + '\n' + error.stack),
35
50
  })
36
51
  }
37
- }
52
+ };
38
53
  start() {
39
54
  this._stopped = false;
40
- }
55
+ };
41
56
  stop() {
42
57
  this._stopped = true;
43
- }
58
+ };
44
59
  shutdown() {
45
60
  this._stopped = true;
46
61
  this._spans = new dataStore();
47
62
  return this.forceFlush();
48
- }
63
+ };
49
64
  /**
50
65
  * Exports any pending spans in the exporter
51
66
  */
52
67
  forceFlush() {
53
68
  return Promise.resolve();
54
- }
69
+ };
55
70
  reset() {
56
71
  this._spans = new dataStore();
57
- }
72
+ };
58
73
  getFinishedSpans() {
59
74
  return this._spans;
75
+ };
76
+ activatePlugin(plugin){
77
+ dbglog(`Activating plugin <${plugin.getName()}>...`);
78
+ InMemoryExporter.plugins.push(plugin);
79
+ dbglog(`Plugin <${plugin.getName()}> active (Total active plugins: ${InMemoryExporter.plugins.length})`);
60
80
  }
61
81
  }
62
82
 
package/src/index.js CHANGED
@@ -1,11 +1,23 @@
1
1
  // telemetryMiddleware.js
2
2
  import { inMemoryExporter } from './telemetry.js';
3
- import { Router } from 'express';
3
+ import { Router,json } from 'express';
4
4
  import v8 from 'v8';
5
5
  import { readFileSync, existsSync } from 'fs';
6
6
  import path from 'path';
7
7
  import yaml from 'js-yaml';
8
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
+ };
9
21
 
10
22
  let baseURL = '/telemetry';
11
23
 
@@ -16,40 +28,42 @@ let telemetryConfig = {
16
28
 
17
29
  export default function oasTelemetry(tlConfig) {
18
30
  if (tlConfig) {
19
- console.log('Telemetry config provided');
31
+ dbglog('Telemetry config provided');
20
32
  telemetryConfig = tlConfig;
21
33
  if (telemetryConfig.exporter == undefined)
22
34
  telemetryConfig.exporter = inMemoryExporter;
23
35
  }
24
36
 
25
37
  if (telemetryConfig.spec)
26
- console.log(`Spec content provided`);
38
+ dbglog(`Spec content provided`);
27
39
  else {
28
40
  if (telemetryConfig.specFileName != "")
29
- console.log(`Spec file used for telemetry: ${telemetryConfig.specFileName}`);
41
+ dbglog(`Spec file used for telemetry: ${telemetryConfig.specFileName}`);
30
42
  else {
31
- console.log("No spec available !");
43
+ console.error("No spec available !");
32
44
  }
33
45
  }
34
46
 
35
-
36
-
37
47
  const router = Router();
38
48
 
39
- //const baseURL = telemetryConfig.baseURL;
49
+ if (telemetryConfig.baseURL)
50
+ baseURL = telemetryConfig.baseURL;
40
51
 
41
- router.get(baseURL, mainPage);
42
- router.get(baseURL + "/detail/*", detailPage);
43
- router.get(baseURL + "/spec", specLoader);
44
- router.get(baseURL + "/api", apiPage);
45
- router.get(baseURL + "/start", startTelemetry);
46
- router.get(baseURL + "/stop", stopTelemetry);
47
- router.get(baseURL + "/status", statusTelemetry);
48
- router.get(baseURL + "/reset", resetTelemetry);
49
- router.get(baseURL + "/list", listTelemetry);
50
- router.post(baseURL + "/find", findTelemetry);
51
- router.get(baseURL + "/heapStats", heapStats);
52
+ router.use(json());
52
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);
53
67
  return router;
54
68
  }
55
69
 
@@ -154,6 +168,19 @@ const listTelemetry = (req, res) => {
154
168
  });
155
169
  }
156
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
+
157
184
  const findTelemetry = (req, res) => {
158
185
  const spansDB = telemetryConfig.exporter.getFinishedSpans();
159
186
  const body = req.body;
@@ -181,17 +208,48 @@ const findTelemetry = (req, res) => {
181
208
  }
182
209
  }
183
210
 
184
- const heapStats = (req, res) => {
185
- var heapStats = v8.getHeapStatistics();
186
-
187
- // Round stats to MB
188
- var roundedHeapStats = Object.getOwnPropertyNames(heapStats).reduce(function (map, stat) {
189
- map[stat] = Math.round((heapStats[stat] / 1024 / 1024) * 1000) / 1000;
190
- return map;
191
- }, {});
192
- roundedHeapStats['units'] = 'MB';
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
+ }
193
220
 
194
- res.send(roundedHeapStats);
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
+
195
252
  }
196
253
 
197
254
 
255
+