@oas-tools/oas-telemetry 0.7.0-alpha.3 → 0.7.0-alpha.4

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 (26) hide show
  1. package/README.md +7 -4
  2. package/dist/cjs/telemetry/custom-implementations/exporters/PluginLogExporter.cjs +2 -9
  3. package/dist/cjs/telemetry/custom-implementations/exporters/PluginMetricExporter.cjs +1 -8
  4. package/dist/cjs/telemetry/custom-implementations/exporters/PluginSpanExporter.cjs +2 -10
  5. package/dist/cjs/tlm-plugin/pluginController.cjs +102 -84
  6. package/dist/cjs/tlm-plugin/pluginProcess.cjs +108 -0
  7. package/dist/cjs/tlm-plugin/pluginRoutes.cjs +3 -0
  8. package/dist/cjs/tlm-plugin/pluginService.cjs +58 -4
  9. package/dist/esm/telemetry/custom-implementations/exporters/PluginLogExporter.js +2 -10
  10. package/dist/esm/telemetry/custom-implementations/exporters/PluginMetricExporter.js +1 -9
  11. package/dist/esm/telemetry/custom-implementations/exporters/PluginSpanExporter.js +2 -11
  12. package/dist/esm/tlm-plugin/pluginController.js +101 -87
  13. package/dist/esm/tlm-plugin/pluginProcess.js +101 -0
  14. package/dist/esm/tlm-plugin/pluginRoutes.js +4 -1
  15. package/dist/esm/tlm-plugin/pluginService.js +58 -4
  16. package/dist/types/config/config.d.ts +610 -5
  17. package/dist/types/tlm-plugin/pluginController.d.ts +4 -1
  18. package/dist/types/tlm-plugin/pluginProcess.d.ts +1 -0
  19. package/dist/types/tlm-plugin/pluginService.d.ts +17 -2
  20. package/dist/types/types/index.d.ts +14 -5
  21. package/dist/ui/assets/index-BzIdRox6.js +1733 -0
  22. package/dist/ui/assets/index-CkoHzrrt.css +1 -0
  23. package/dist/ui/index.html +2 -2
  24. package/package.json +2 -2
  25. package/dist/ui/assets/index-D9HsRlaQ.js +0 -437
  26. package/dist/ui/assets/index-DEyIcKBi.css +0 -1
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # OAS TELEMETRY
2
2
 
3
- **OAS Telemetry** is an Express middleware for collecting telemetry data using **OpenTelemetry** in **OpenAPI Specification (OAS)**-based applications. It exposes endpoints for managing and analyzing telemetry data—such as starting/stopping collection, resetting, listing, and searching records—making integration with **Express.js** straightforward.
3
+
4
+ **OAS Telemetry** is a library that automatically configures telemetry in your Express application based on OpenAPI, with no extra code required. Simply use the middleware to instantly access endpoints for viewing recent requests, system logs, and metrics—all stored in memory. This allows you to analyze your API’s behavior and debug issues easily, without manual setup or complex integration. OpenTelemetry is used under the hood to collect traces, metrics, and logs.
4
5
 
5
6
  The middleware is highly configurable and supports both **ES Module (ESM)** and **CommonJS (CJS)** formats (ESM targeting **ES2020**). Its functionality can be extended via plugins; see [Telemetry Plugins](#telemetry-plugins) for details.
6
7
 
@@ -8,11 +9,13 @@ The middleware is highly configurable and supports both **ES Module (ESM)** and
8
9
  >
9
10
  > **OAS Telemetry** is a functional and working package, but it is currently at version 0 and remains under active development. Features, APIs, and behavior are subject to change at any time. Please review the following current status before use:
10
11
  >
11
- > - **Traces:** Semi-stable. Currently supports HTTP instrumentation.
12
+ > - **Traces:** Semi-stable. Currently supports HTTP instrumentation. We are studying switching to auto-instrumentation, but we need to test memory usage and performance first.
12
13
  > - **Logs:** Semi-stable. Supports fast search by message content and Mongo-like search (similar to traces).
13
- > - **Metrics:** Semi=stable. Currently uses OpenTelemetry host metrics. This area is subject to change to improve memory usage and data handling.
14
+ > - **Metrics:** Not stable. Currently uses OpenTelemetry host metrics. This area is subject to change to improve memory usage and data handling.
14
15
  > - **Configuration:** Semi-stable. All configuration options will be available as parameters at initialization and via environment variables (see the `.env.example` file). The configuration system is under active development and will change significantly in future releases.
15
- > - **UI:** Not stable. Migration to React is in progress. Most views are placeholders except for the AI agent, which is fully functional and can answer questions about metrics, traces, and logs.
16
+ > - **UI:** Not stable. Migration to React is in progress. Most views are placeholders except for the AI agent, which is fully functional and can answer questions about traces, and logs (Not yet for metrics). Plugin management page is also functional. Next steps include logs page (almost done) and traces page (subject to change based on instrumentation approach). Metrics page will be the last to be implemented, as we want to support custom metrics in the future.
17
+ >
18
+ > Please if you want to use this package contact us via motero6@us.es
16
19
 
17
20
  ## Usage
18
21
 
@@ -24,15 +24,8 @@ class PluginLogExporter extends _wrappers.Enabler {
24
24
  });
25
25
  }
26
26
  const cleanLogs = logs.map(log => (0, _circular.removeCircularRefs)(log)).map(log => (0, _circular.applyNesting)(log));
27
- _pluginService.pluginService.getPlugins().forEach((pluginResource, i) => {
28
- if (typeof pluginResource.pluginImplementation.newLog === 'function') {
29
- cleanLogs.forEach(log => {
30
- _logger.default.debug(`Sending log to plugin (Plugin #${i}) <${pluginResource.name}>`);
31
- pluginResource.pluginImplementation.newLog(log);
32
- });
33
- } else {
34
- _logger.default.debug(`Plugin <${pluginResource.name}> does not implement newLog method. Skipping log export.`);
35
- }
27
+ cleanLogs.forEach(log => {
28
+ _pluginService.pluginService.broadcastLog(log);
36
29
  });
37
30
  setTimeout(() => resultCallback({
38
31
  code: _core.ExportResultCode.SUCCESS
@@ -24,14 +24,7 @@ class PluginMetricExporter extends _wrappers.Enabler {
24
24
  });
25
25
  }
26
26
  const cleanMetrics = (0, _circular.applyNesting)((0, _circular.removeCircularRefs)(metrics));
27
- _pluginService.pluginService.getPlugins().forEach((pluginResource, i) => {
28
- if (typeof pluginResource.pluginImplementation.newMetric === 'function') {
29
- _logger.default.debug(`Sending metric to plugin (Plugin #${i}) <${pluginResource.name}>`);
30
- pluginResource.pluginImplementation.newMetric(cleanMetrics);
31
- } else {
32
- _logger.default.debug(`Plugin <${pluginResource.name}> does not implement newMetric method. Skipping metric export.`);
33
- }
34
- });
27
+ _pluginService.pluginService.broadcastMetric(cleanMetrics);
35
28
  setTimeout(() => resultCallback({
36
29
  code: _core.ExportResultCode.SUCCESS
37
30
  }), 0);
@@ -37,16 +37,8 @@ class PluginSpanExporter extends _wrappers.Enabler {
37
37
  }
38
38
  return true;
39
39
  });
40
- _pluginService.pluginService.getPlugins().forEach((pluginResource, i) => {
41
- if (typeof pluginResource.pluginImplementation.newTrace === 'function') {
42
- cleanSpans.forEach(span => {
43
- _logger.default.debug(`Sending span to plugin (Plugin #${i}) <${pluginResource.name}>`);
44
- //TODO: This should be called newSpan instead of newTrace
45
- pluginResource.pluginImplementation.newTrace(span);
46
- });
47
- } else {
48
- _logger.default.debug(`Plugin <${pluginResource.name}> does not implement newTrace method. Skipping span export.`);
49
- }
40
+ cleanSpans.forEach(span => {
41
+ _pluginService.pluginService.broadcastTrace(span);
50
42
  });
51
43
  setTimeout(() => resultCallback({
52
44
  code: _core.ExportResultCode.SUCCESS
@@ -3,127 +3,145 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.registerPlugin = exports.listPlugins = void 0;
6
+ exports.registerPlugin = exports.listPlugins = exports.deletePlugin = exports.deactivatePlugin = exports.activatePlugin = void 0;
7
7
  var _axios = _interopRequireDefault(require("axios"));
8
- var _importFromString = require("import-from-string");
9
- var _dynamicInstaller = require("dynamic-installer");
8
+ var _child_process = require("child_process");
9
+ var _path = _interopRequireDefault(require("path"));
10
10
  var _logger = _interopRequireDefault(require("../utils/logger.cjs"));
11
11
  var _pluginService = require("./pluginService.cjs");
12
+ var _url = require("url");
12
13
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
- // @ts-expect-error: import-from-string does not have proper type declarations
14
-
15
- // @ts-expect-error: dynamic-installer does not have proper type declarations
16
-
17
14
  const listPlugins = (req, res) => {
18
- const plugins = _pluginService.pluginService.getPlugins().map(plugin => {
19
- return {
20
- id: plugin.id,
21
- name: plugin.name,
22
- url: plugin.url,
23
- active: plugin.active
24
- };
25
- });
15
+ const plugins = _pluginService.pluginService.getPlugins();
26
16
  res.send({
27
17
  pluginsCount: plugins.length,
28
- plugins: plugins
18
+ plugins
29
19
  });
30
20
  };
31
21
  exports.listPlugins = listPlugins;
32
22
  const registerPlugin = async (req, res) => {
33
- let pluginCode;
34
23
  const pluginResource = req.body;
35
- _logger.default.debug(`Plugin Registration Request: = ${JSON.stringify(req.body, null, 2)}...`);
36
- // Validate plugin id
24
+ _logger.default.debug(`Plugin Registration Request: ${JSON.stringify(req.body, null, 2)}...`);
25
+ // Validate id
37
26
  if (!pluginResource.id || typeof pluginResource.id !== "string") {
38
- res.status(400).send(`Plugin id must be provided and must be a string`);
27
+ res.status(400).send("Plugin id must be provided and must be a string");
39
28
  return;
40
29
  }
41
- // Check for duplicate plugin id
42
- const existingPlugin = _pluginService.pluginService.getPlugins().find(plugin => plugin.id === pluginResource.id);
43
- if (existingPlugin) {
44
- res.status(400).send(`A plugin with id "${pluginResource.id}" already exists.`);
30
+ // Check duplicate
31
+ if (_pluginService.pluginService.getPlugins().find(p => p.id === pluginResource.id)) {
32
+ res.status(400).send(`Plugin with id "${pluginResource.id}" already exists.`);
45
33
  return;
46
34
  }
35
+ // Validate inputs
47
36
  if (!pluginResource.url && !pluginResource.code) {
48
- res.status(400).send(`Plugin code or URL must be provided`);
37
+ res.status(400).send("Plugin code or URL must be provided");
49
38
  return;
50
39
  }
51
40
  if (!pluginResource.moduleFormat) {
52
- res.status(400).send(`Plugin moduleFormat must be provided (e.g., "cjs" or "esm")`);
41
+ res.status(400).send("Plugin moduleFormat must be provided (cjs|esm)");
53
42
  return;
54
43
  }
55
- if (!["cjs", "esm"].includes(pluginResource.moduleFormat.toLowerCase())) {
56
- res.status(400).send(`Invalid moduleFormat "${pluginResource.moduleFormat}". Supported formats are "cjs" and "esm".`);
57
- return;
58
- }
59
- let module;
44
+ // Fetch code
45
+ let pluginCode;
60
46
  try {
61
47
  if (pluginResource.code) {
62
48
  pluginCode = pluginResource.code;
63
49
  } else {
50
+ console.log(pluginResource.url);
64
51
  const response = await _axios.default.get(pluginResource.url);
65
52
  pluginCode = response.data;
66
53
  }
67
- if (!pluginCode) {
68
- res.status(400).send(`Plugin code could not be loaded`);
69
- return;
70
- }
71
- if (pluginResource.install) {
72
- _logger.default.info("Installing dependencies for plugin: " + pluginResource.name);
73
- const dependenciesStatus = await (0, _dynamicInstaller.installDependencies)(pluginResource.install);
74
- if (!dependenciesStatus.success) {
75
- if (pluginResource.install.ignoreErrors === true) {
76
- _logger.default.warn(`Warning: Error installing dependencies: ${JSON.stringify(dependenciesStatus.details)}`);
77
- } else {
78
- res.status(400).send(`Error installing dependencies: ${JSON.stringify(dependenciesStatus.details)}`);
79
- return;
80
- }
81
- }
82
- }
83
- _logger.default.debug("Plugin format (provided): " + pluginResource?.moduleFormat);
84
- if (pluginResource.moduleFormat.toLowerCase() === "esm") {
85
- _logger.default.info("ESM detected");
86
- module = await (0, _importFromString.importFromString)(pluginCode);
87
- } else {
88
- _logger.default.info("CJS detected (default)");
89
- module = await (0, _importFromString.requireFromString)(pluginCode);
90
- }
91
- } catch (error) {
92
- _logger.default.error(`Error loading plugin: ${error}`);
93
- res.status(400).send(`Error loading plugin: ${error}`);
54
+ pluginResource.sourceCode = pluginCode;
55
+ } catch (err) {
56
+ res.status(400).send(`Error fetching plugin code: ${err}`);
94
57
  return;
95
58
  }
96
- const plugin = module.default?.plugin ?? module.plugin;
97
- if (!plugin) {
98
- res.status(400).send(`Plugin code should export a valid "plugin" object or static class`);
99
- _logger.default.info("Error in plugin code: no valid plugin object exported");
59
+ if (!pluginCode) {
60
+ res.status(400).send("Plugin code could not be loaded");
100
61
  return;
101
62
  }
102
- for (const requiredFunction of ["load", "getName", "isConfigured"]) {
103
- if (typeof plugin[requiredFunction] !== "function") {
104
- res.status(400).send(`The plugin code exports a "plugin" object, but it must have a "${requiredFunction}" method`);
105
- _logger.default.info("Error in plugin code: some required functions are missing");
106
- return;
63
+ const isCjs = typeof __filename !== "undefined" && typeof __dirname !== "undefined";
64
+ const __filenameUniversal = isCjs ? __filename : (0, _url.fileURLToPath)(require('url').pathToFileURL(__filename).toString());
65
+ const __dirnameUniversal = isCjs ? __dirname : _path.default.dirname(__filenameUniversal);
66
+ const pluginProcessFile = isCjs ? "pluginProcess.cjs" : "pluginProcess.js";
67
+ const child = (0, _child_process.fork)(_path.default.resolve(__dirnameUniversal, pluginProcessFile), [], {
68
+ stdio: ["pipe", "pipe", "pipe", "ipc"]
69
+ });
70
+ child.stdout?.on("data", data => {
71
+ _logger.default.info(`[Plugin ${pluginResource.id}] STDOUT: ${data.toString().trim()}`);
72
+ });
73
+ child.stderr?.on("data", data => {
74
+ _logger.default.error(`[Plugin ${pluginResource.id}] STDERR: ${data.toString().trim()}`);
75
+ });
76
+ child.on("message", msg => {
77
+ if (msg.event === "loaded") {
78
+ pluginResource.name = msg.name;
79
+ pluginResource.active = true;
80
+ pluginResource.process = child;
81
+ _pluginService.pluginService.pushPlugin(pluginResource);
82
+ res.status(201).send(`Plugin ${msg.name} registered`);
83
+ } else if (msg.event === "error") {
84
+ res.status(400).send(`Error loading plugin: ${msg.error}`);
107
85
  }
86
+ });
87
+ child.on("exit", code => {
88
+ pluginResource.active = false;
89
+ pluginResource.process = undefined;
90
+ _logger.default.warn(`Plugin ${pluginResource.id} exited (code: ${code})`);
91
+ });
92
+ child.on("disconnect", () => {
93
+ pluginResource.active = false;
94
+ pluginResource.process = undefined;
95
+ _logger.default.warn(`Plugin ${pluginResource.id} disconnected`);
96
+ });
97
+ child.on("error", err => {
98
+ pluginResource.active = false;
99
+ pluginResource.process = undefined;
100
+ _logger.default.error(`Plugin ${pluginResource.id} error: ${err.message}`);
101
+ });
102
+ // Send data to child
103
+ child.send({
104
+ type: "load",
105
+ pluginResource
106
+ });
107
+ };
108
+ exports.registerPlugin = registerPlugin;
109
+ const activatePlugin = (req, res) => {
110
+ const {
111
+ id
112
+ } = req.params;
113
+ const plugin = _pluginService.pluginService.getPlugins().find(p => p.id === id);
114
+ if (!plugin) {
115
+ res.status(404).send(`Plugin with id "${id}" not found.`);
116
+ return;
108
117
  }
109
- try {
110
- await plugin.load(pluginResource.config);
111
- } catch (error) {
112
- _logger.default.error(`Error loading plugin configuration: ${error}`);
113
- res.status(400).send(`Error loading plugin configuration: ${error}`);
118
+ _pluginService.pluginService.activatePlugin(id);
119
+ res.status(200).send(`Plugin "${id}" activated.`);
120
+ };
121
+ exports.activatePlugin = activatePlugin;
122
+ const deactivatePlugin = (req, res) => {
123
+ const {
124
+ id
125
+ } = req.params;
126
+ const plugin = _pluginService.pluginService.getPlugins().find(p => p.id === id);
127
+ if (!plugin) {
128
+ res.status(404).send(`Plugin with id "${id}" not found.`);
114
129
  return;
115
130
  }
116
- if (plugin.isConfigured()) {
117
- _logger.default.info(`Loaded plugin <${plugin.getName()}>`);
118
- pluginResource.pluginImplementation = plugin;
119
- pluginResource.name = plugin.getName();
120
- pluginResource.active = true;
121
- _pluginService.pluginService.pushPlugin(pluginResource);
122
- _pluginService.pluginService.activatePlugin(pluginResource);
123
- res.status(201).send(`Plugin registered`);
124
- } else {
125
- _logger.default.error(`Plugin <${plugin.getName()}> cannot be configured`);
126
- res.status(400).send(`Plugin configuration problem`);
131
+ _pluginService.pluginService.deactivatePlugin(id); // This only sets active to false
132
+ res.status(200).send(`Plugin "${id}" deactivated.`);
133
+ };
134
+ exports.deactivatePlugin = deactivatePlugin;
135
+ const deletePlugin = (req, res) => {
136
+ const {
137
+ id
138
+ } = req.params;
139
+ const plugin = _pluginService.pluginService.getPlugins().find(p => p.id === id);
140
+ if (!plugin) {
141
+ res.status(404).send(`Plugin with id "${id}" not found.`);
142
+ return;
127
143
  }
144
+ _pluginService.pluginService.deletePlugin(id); // kills child inside service
145
+ res.status(200).send(`Plugin "${id}" deleted.`);
128
146
  };
129
- exports.registerPlugin = registerPlugin;
147
+ exports.deletePlugin = deletePlugin;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+
3
+ var _importFromString = require("import-from-string");
4
+ var _dynamicInstaller = require("dynamic-installer");
5
+ // pluginProcess.js
6
+ // Runs inside a child process, isolated from the main app
7
+ // @ts-expect-error no types
8
+
9
+ let plugin;
10
+ const log = (...args) => {
11
+ console.log(`[PluginProcess:${process.pid}]`, ...args);
12
+ };
13
+ process.on("message", async msg => {
14
+ if (msg.type === "load") {
15
+ try {
16
+ const pluginResource = normalizePluginResource(msg.pluginResource);
17
+ if (pluginResource.install && Array.isArray(pluginResource.install.dependencies) && pluginResource.install.dependencies.length > 0) {
18
+ log("Installing dependencies for plugin: " + pluginResource.name);
19
+ const dependenciesStatus = await (0, _dynamicInstaller.installDependencies)(pluginResource.install);
20
+ if (!dependenciesStatus.success) {
21
+ if (pluginResource.install.ignoreErrors === true) {
22
+ log(`Warning: Error installing dependencies: ${JSON.stringify(dependenciesStatus.details)}`);
23
+ } else {
24
+ process.send?.({
25
+ event: "error",
26
+ error: `Error installing dependencies: ${JSON.stringify(dependenciesStatus.details)}`
27
+ });
28
+ return;
29
+ }
30
+ }
31
+ }
32
+ let module;
33
+ if (pluginResource?.moduleFormat?.toLowerCase() === "esm") {
34
+ module = await (0, _importFromString.importFromString)(pluginResource.sourceCode);
35
+ } else {
36
+ module = await (0, _importFromString.requireFromString)(pluginResource.sourceCode);
37
+ }
38
+ plugin = module.default?.plugin ?? module.plugin;
39
+ if (!plugin) throw new Error("Plugin must export a valid 'plugin' object");
40
+ for (const fn of ["load", "isConfigured"]) {
41
+ if (typeof plugin[fn] !== "function") {
42
+ throw new Error(`Plugin is missing required function "${fn}"`);
43
+ }
44
+ }
45
+ await plugin.load(pluginResource.config);
46
+ if (!plugin.isConfigured()) {
47
+ throw new Error("Plugin could not be configured");
48
+ }
49
+ process.send?.({
50
+ event: "loaded",
51
+ name: pluginResource.name || pluginResource.id || "unknown"
52
+ });
53
+ } catch (err) {
54
+ process.send?.({
55
+ event: "error",
56
+ error: err.message
57
+ });
58
+ process.exit(1);
59
+ }
60
+ }
61
+ // Forward log/metric/trace calls
62
+ if (msg.type === "newLog" && plugin?.newLog) {
63
+ plugin.newLog(msg.payload);
64
+ }
65
+ if (msg.type === "newMetric" && plugin?.newMetric) {
66
+ plugin.newMetric(msg.payload);
67
+ }
68
+ if (msg.type === "newTrace" && plugin?.newTrace) {
69
+ plugin.newTrace(msg.payload);
70
+ }
71
+ if (msg.type === "unload") {
72
+ if (plugin && typeof plugin.unload === "function") {
73
+ await plugin.unload();
74
+ }
75
+ process.send?.({
76
+ event: "unloaded"
77
+ });
78
+ process.exit(0);
79
+ }
80
+ });
81
+ function normalizePluginResource(raw) {
82
+ let resource = raw;
83
+ // case: received as stringified JSON
84
+ if (typeof raw === "string") {
85
+ try {
86
+ resource = JSON.parse(raw);
87
+ } catch (err) {
88
+ throw new Error("Invalid pluginResource JSON: " + err.message);
89
+ }
90
+ }
91
+ // normalize install
92
+ if (typeof resource.install === "string") {
93
+ try {
94
+ resource.install = JSON.parse(resource.install);
95
+ } catch (err) {
96
+ throw new Error("Invalid install JSON: " + err.message);
97
+ }
98
+ }
99
+ // normalize config
100
+ if (typeof resource.config === "string") {
101
+ try {
102
+ resource.config = JSON.parse(resource.config);
103
+ } catch (err) {
104
+ throw new Error("Invalid config JSON: " + err.message);
105
+ }
106
+ }
107
+ return resource;
108
+ }
@@ -10,6 +10,9 @@ const getPluginRoutes = () => {
10
10
  const router = (0, _express.Router)();
11
11
  router.get('/', _pluginController.listPlugins);
12
12
  router.post('/', _pluginController.registerPlugin);
13
+ router.post('/:id/activate', _pluginController.activatePlugin);
14
+ router.post('/:id/deactivate', _pluginController.deactivatePlugin);
15
+ router.delete('/:id', _pluginController.deletePlugin);
13
16
  return router;
14
17
  };
15
18
  exports.getPluginRoutes = getPluginRoutes;
@@ -4,6 +4,8 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.pluginService = void 0;
7
+ var _logger = _interopRequireDefault(require("../utils/logger.cjs"));
8
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
9
  class PluginService {
8
10
  constructor() {
9
11
  this.plugins = [];
@@ -14,12 +16,64 @@ class PluginService {
14
16
  pushPlugin(plugin) {
15
17
  this.plugins.push(plugin);
16
18
  }
17
- activatePlugin(plugin) {
18
- this.plugins.forEach(p => {
19
- if (p.id === plugin.id) {
20
- p.active = true;
19
+ activatePlugin(pluginId) {
20
+ const plugin = this.plugins.find(p => p.id === pluginId);
21
+ if (plugin) {
22
+ plugin.active = true;
23
+ }
24
+ }
25
+ deactivatePlugin(pluginId) {
26
+ const plugin = this.plugins.find(p => p.id === pluginId);
27
+ if (plugin) {
28
+ plugin.active = false;
29
+ }
30
+ }
31
+ deletePlugin(pluginId) {
32
+ const plugin = this.plugins.find(p => p.id === pluginId);
33
+ if (plugin?.process && !plugin.process.killed) {
34
+ plugin.process.kill(1);
35
+ }
36
+ this.plugins = this.plugins.filter(p => p.id !== pluginId);
37
+ }
38
+ broadcastToPlugins(type, payload) {
39
+ this.plugins.forEach((plugin, i) => {
40
+ if (!plugin.active) return;
41
+ if (plugin.process) {
42
+ if (plugin.process.connected) {
43
+ try {
44
+ plugin.process.send({
45
+ type,
46
+ payload
47
+ });
48
+ _logger.default.debug(`Sent ${type} to child-process plugin <${plugin.name}> (#${i})`);
49
+ } catch (err) {
50
+ _logger.default.error(`Failed to send ${type} to plugin <${plugin.name}> (#${i}):`, err);
51
+ }
52
+ } else {
53
+ _logger.default.warn(`Plugin <${plugin.name}> (#${i}) is not connected. Skipping ${type}.`);
54
+ }
55
+ } else {
56
+ _logger.default.debug(`Plugin <${plugin.name}> does not implement ${type}. Skipping.`);
21
57
  }
22
58
  });
23
59
  }
60
+ /**
61
+ * Broadcast a new metric to all active plugins
62
+ */
63
+ broadcastMetric(metric) {
64
+ this.broadcastToPlugins("newMetric", metric);
65
+ }
66
+ /**
67
+ * Broadcast a new log to all active plugins
68
+ */
69
+ broadcastLog(log) {
70
+ this.broadcastToPlugins("newLog", log);
71
+ }
72
+ /**
73
+ * Broadcast a new trace to all active plugins
74
+ */
75
+ broadcastTrace(trace) {
76
+ this.broadcastToPlugins("newTrace", trace);
77
+ }
24
78
  }
25
79
  const pluginService = exports.pluginService = new PluginService();
@@ -17,16 +17,8 @@ export class PluginLogExporter extends Enabler {
17
17
  const cleanLogs = logs
18
18
  .map(log => removeCircularRefs(log))
19
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
- }
20
+ cleanLogs.forEach(log => {
21
+ pluginService.broadcastLog(log);
30
22
  });
31
23
  setTimeout(() => resultCallback({ code: ExportResultCode.SUCCESS }), 0);
32
24
  }
@@ -15,15 +15,7 @@ export class PluginMetricExporter extends Enabler {
15
15
  return resultCallback({ code: ExportResultCode.SUCCESS });
16
16
  }
17
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
- });
18
+ pluginService.broadcastMetric(cleanMetrics);
27
19
  setTimeout(() => resultCallback({ code: ExportResultCode.SUCCESS }), 0);
28
20
  }
29
21
  catch (error) {
@@ -30,17 +30,8 @@ export class PluginSpanExporter extends Enabler {
30
30
  }
31
31
  return true;
32
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
- }
33
+ cleanSpans.forEach(span => {
34
+ pluginService.broadcastTrace(span);
44
35
  });
45
36
  setTimeout(() => resultCallback({ code: ExportResultCode.SUCCESS }), 0);
46
37
  }