@oas-tools/oas-telemetry 0.7.0-alpha.2 → 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 (158) hide show
  1. package/.env.example +50 -17
  2. package/README.md +244 -239
  3. package/dist/cjs/config/bootConfig.cjs +18 -0
  4. package/dist/cjs/config/config.cjs +145 -0
  5. package/dist/cjs/config/config.types.cjs +5 -0
  6. package/dist/cjs/index.cjs +19 -25
  7. package/dist/cjs/{tlmRoutes.cjs → routesManager.cjs} +28 -21
  8. package/dist/cjs/{exporters/InMemoryLogRecordExporter.cjs → telemetry/custom-implementations/exporters/InMemoryDbLogExporter.cjs} +42 -19
  9. package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.cjs +97 -0
  10. package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.cjs +118 -0
  11. package/dist/cjs/telemetry/custom-implementations/exporters/PluginLogExporter.cjs +45 -0
  12. package/dist/cjs/telemetry/custom-implementations/exporters/PluginMetricExporter.cjs +46 -0
  13. package/dist/cjs/telemetry/custom-implementations/exporters/PluginSpanExporter.cjs +61 -0
  14. package/dist/cjs/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.cjs +70 -0
  15. package/dist/cjs/telemetry/custom-implementations/processors/dynamicMultiSpanProcessor.cjs +70 -0
  16. package/dist/cjs/{utils → telemetry/custom-implementations/utils}/circular.cjs +39 -49
  17. package/dist/cjs/telemetry/custom-implementations/wrappers.cjs +175 -0
  18. package/dist/cjs/telemetry/initializeTelemetry.cjs +74 -0
  19. package/dist/cjs/telemetry/telemetryConfigurator.cjs +84 -0
  20. package/dist/cjs/telemetry/telemetryRegistry.cjs +40 -0
  21. package/dist/cjs/tlm-ai/agent.cjs +82 -63
  22. package/dist/cjs/tlm-ai/aiController.cjs +5 -4
  23. package/dist/cjs/tlm-ai/aiRoutes.cjs +9 -6
  24. package/dist/cjs/tlm-ai/tools.cjs +16 -9
  25. package/dist/cjs/tlm-auth/authController.cjs +14 -15
  26. package/dist/cjs/tlm-auth/authMiddleware.cjs +11 -10
  27. package/dist/cjs/tlm-auth/authRoutes.cjs +9 -7
  28. package/dist/cjs/tlm-log/logController.cjs +45 -18
  29. package/dist/cjs/tlm-log/logRoutes.cjs +16 -11
  30. package/dist/cjs/tlm-metric/metricsController.cjs +37 -12
  31. package/dist/cjs/tlm-metric/metricsRoutes.cjs +16 -11
  32. package/dist/cjs/tlm-plugin/pluginController.cjs +114 -75
  33. package/dist/cjs/tlm-plugin/pluginProcess.cjs +108 -0
  34. package/dist/cjs/tlm-plugin/pluginRoutes.cjs +11 -6
  35. package/dist/cjs/tlm-plugin/pluginService.cjs +79 -0
  36. package/dist/cjs/tlm-trace/traceController.cjs +54 -45
  37. package/dist/cjs/tlm-trace/traceRoutes.cjs +16 -11
  38. package/dist/cjs/tlm-ui/uiRoutes.cjs +26 -20
  39. package/dist/cjs/tlm-util/utilController.cjs +8 -9
  40. package/dist/cjs/tlm-util/utilRoutes.cjs +22 -19
  41. package/dist/cjs/types/index.cjs +0 -1
  42. package/dist/cjs/utils/logger.cjs +3 -5
  43. package/dist/cjs/utils/regexUtils.cjs +27 -0
  44. package/dist/esm/config/bootConfig.js +11 -0
  45. package/dist/esm/config/config.js +126 -0
  46. package/dist/esm/index.js +18 -29
  47. package/dist/esm/{tlmRoutes.js → routesManager.js} +27 -22
  48. package/dist/esm/{exporters/InMemoryLogRecordExporter.js → telemetry/custom-implementations/exporters/InMemoryDbLogExporter.js} +31 -17
  49. package/dist/esm/{exporters/InMemoryDBMetricsExporter.js → telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.js} +31 -17
  50. package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.js +105 -0
  51. package/dist/esm/telemetry/custom-implementations/exporters/PluginLogExporter.js +36 -0
  52. package/dist/esm/telemetry/custom-implementations/exporters/PluginMetricExporter.js +35 -0
  53. package/dist/esm/telemetry/custom-implementations/exporters/PluginSpanExporter.js +52 -0
  54. package/dist/esm/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.js +64 -0
  55. package/dist/esm/telemetry/custom-implementations/processors/dynamicMultiSpanProcessor.js +64 -0
  56. package/dist/esm/telemetry/custom-implementations/utils/circular.js +76 -0
  57. package/dist/esm/telemetry/custom-implementations/wrappers.js +163 -0
  58. package/dist/esm/telemetry/initializeTelemetry.js +71 -0
  59. package/dist/esm/telemetry/telemetryConfigurator.js +74 -0
  60. package/dist/esm/telemetry/telemetryRegistry.js +34 -0
  61. package/dist/esm/tlm-ai/agent.js +77 -59
  62. package/dist/esm/tlm-ai/aiController.js +5 -4
  63. package/dist/esm/tlm-ai/aiRoutes.js +7 -5
  64. package/dist/esm/tlm-ai/tools.js +18 -9
  65. package/dist/esm/tlm-auth/authController.js +9 -10
  66. package/dist/esm/tlm-auth/authMiddleware.js +10 -9
  67. package/dist/esm/tlm-auth/authRoutes.js +8 -6
  68. package/dist/esm/tlm-log/logController.js +36 -16
  69. package/dist/esm/tlm-log/logRoutes.js +15 -11
  70. package/dist/esm/tlm-metric/metricsController.js +29 -10
  71. package/dist/esm/tlm-metric/metricsRoutes.js +15 -11
  72. package/dist/esm/tlm-plugin/pluginController.js +112 -77
  73. package/dist/esm/tlm-plugin/pluginProcess.js +101 -0
  74. package/dist/esm/tlm-plugin/pluginRoutes.js +10 -6
  75. package/dist/esm/tlm-plugin/pluginService.js +73 -0
  76. package/dist/esm/tlm-trace/traceController.js +40 -35
  77. package/dist/esm/tlm-trace/traceRoutes.js +15 -11
  78. package/dist/esm/tlm-ui/uiRoutes.js +24 -19
  79. package/dist/esm/tlm-util/utilController.js +8 -9
  80. package/dist/esm/tlm-util/utilRoutes.js +17 -15
  81. package/dist/esm/types/index.js +0 -1
  82. package/dist/esm/utils/logger.js +3 -4
  83. package/dist/esm/utils/regexUtils.js +23 -0
  84. package/dist/types/config/bootConfig.d.ts +5 -0
  85. package/dist/types/config/config.d.ts +858 -0
  86. package/dist/types/config/config.types.d.ts +34 -0
  87. package/dist/types/index.d.ts +5 -4
  88. package/dist/types/routesManager.d.ts +3 -0
  89. package/dist/types/{exporters/InMemoryLogRecordExporter.d.ts → telemetry/custom-implementations/exporters/InMemoryDbLogExporter.d.ts} +6 -6
  90. package/dist/types/{exporters/InMemoryDBMetricsExporter.d.ts → telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.d.ts} +7 -7
  91. package/dist/types/{exporters/InMemoryDbExporter.d.ts → telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.d.ts} +9 -9
  92. package/dist/types/telemetry/custom-implementations/exporters/PluginLogExporter.d.ts +8 -0
  93. package/dist/types/telemetry/custom-implementations/exporters/PluginMetricExporter.d.ts +12 -0
  94. package/dist/types/telemetry/custom-implementations/exporters/PluginSpanExporter.d.ts +14 -0
  95. package/dist/types/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.d.ts +32 -0
  96. package/dist/types/telemetry/custom-implementations/processors/dynamicMultiSpanProcessor.d.ts +34 -0
  97. package/dist/types/telemetry/custom-implementations/utils/circular.d.ts +27 -0
  98. package/dist/types/telemetry/custom-implementations/wrappers.d.ts +52 -0
  99. package/dist/types/telemetry/initializeTelemetry.d.ts +1 -0
  100. package/dist/types/telemetry/telemetryConfigurator.d.ts +2 -0
  101. package/dist/types/telemetry/telemetryRegistry.d.ts +20 -0
  102. package/dist/types/tlm-ai/agent.d.ts +2 -1
  103. package/dist/types/tlm-ai/aiController.d.ts +4 -3
  104. package/dist/types/tlm-ai/aiRoutes.d.ts +2 -2
  105. package/dist/types/tlm-ai/tools.d.ts +3 -1
  106. package/dist/types/tlm-auth/authController.d.ts +4 -3
  107. package/dist/types/tlm-auth/authMiddleware.d.ts +2 -1
  108. package/dist/types/tlm-auth/authRoutes.d.ts +2 -2
  109. package/dist/types/tlm-log/logController.d.ts +1 -0
  110. package/dist/types/tlm-log/logRoutes.d.ts +2 -2
  111. package/dist/types/tlm-metric/metricsController.d.ts +1 -0
  112. package/dist/types/tlm-metric/metricsRoutes.d.ts +2 -2
  113. package/dist/types/tlm-plugin/pluginController.d.ts +4 -1
  114. package/dist/types/tlm-plugin/pluginProcess.d.ts +1 -0
  115. package/dist/types/tlm-plugin/pluginRoutes.d.ts +1 -2
  116. package/dist/types/tlm-plugin/pluginService.d.ts +24 -0
  117. package/dist/types/tlm-trace/traceController.d.ts +7 -6
  118. package/dist/types/tlm-trace/traceRoutes.d.ts +2 -2
  119. package/dist/types/tlm-ui/uiRoutes.d.ts +1 -2
  120. package/dist/types/tlm-util/utilController.d.ts +2 -1
  121. package/dist/types/tlm-util/utilRoutes.d.ts +2 -2
  122. package/dist/types/types/index.d.ts +17 -47
  123. package/dist/types/utils/regexUtils.d.ts +1 -0
  124. package/dist/ui/assets/index-BzIdRox6.js +1733 -0
  125. package/dist/ui/assets/index-CkoHzrrt.css +1 -0
  126. package/dist/ui/index.html +3 -3
  127. package/dist/ui/oas-tlm.svg +185 -0
  128. package/package.json +12 -7
  129. package/dist/cjs/config.cjs +0 -31
  130. package/dist/cjs/exporters/InMemoryDBMetricsExporter.cjs +0 -74
  131. package/dist/cjs/exporters/InMemoryDbExporter.cjs +0 -102
  132. package/dist/cjs/exporters/consoleExporter.cjs +0 -47
  133. package/dist/cjs/exporters/dynamicExporter.cjs +0 -57
  134. package/dist/cjs/instrumentation/index.cjs +0 -28
  135. package/dist/cjs/instrumentation/logs.cjs +0 -46
  136. package/dist/cjs/instrumentation/metrics.cjs +0 -27
  137. package/dist/cjs/instrumentation/traces.cjs +0 -19
  138. package/dist/esm/config.js +0 -20
  139. package/dist/esm/exporters/InMemoryDbExporter.js +0 -102
  140. package/dist/esm/exporters/consoleExporter.js +0 -38
  141. package/dist/esm/exporters/dynamicExporter.js +0 -50
  142. package/dist/esm/instrumentation/index.js +0 -26
  143. package/dist/esm/instrumentation/logs.js +0 -34
  144. package/dist/esm/instrumentation/metrics.js +0 -18
  145. package/dist/esm/instrumentation/traces.js +0 -12
  146. package/dist/esm/utils/circular.js +0 -84
  147. package/dist/types/config.d.ts +0 -6
  148. package/dist/types/exporters/consoleExporter.d.ts +0 -13
  149. package/dist/types/exporters/dynamicExporter.d.ts +0 -25
  150. package/dist/types/instrumentation/logs.d.ts +0 -1
  151. package/dist/types/instrumentation/metrics.d.ts +0 -1
  152. package/dist/types/instrumentation/traces.d.ts +0 -1
  153. package/dist/types/tlmRoutes.d.ts +0 -2
  154. package/dist/types/utils/circular.d.ts +0 -31
  155. package/dist/ui/assets/index-BNhZBPi2.css +0 -1
  156. package/dist/ui/assets/index-DxGAMrAl.js +0 -401
  157. package/dist/ui/vite.svg +0 -1
  158. /package/dist/{types/instrumentation/index.d.ts → esm/config/config.types.js} +0 -0
@@ -0,0 +1,76 @@
1
+ export function removeCircularRefs(obj) {
2
+ // const seen = new WeakMap(); // Used to keep track of visited objects
3
+ // Replacer function to handle circular references
4
+ function replacer(key, value) {
5
+ if (key === "_spanProcessor") {
6
+ return "oas-telemetry skips this field to avoid circular reference";
7
+ }
8
+ // GENERIC CIRCULAR REFERENCE HANDLING
9
+ // if (typeof value === "object" && value !== null) {
10
+ // // If the object has been visited before, return the name prefixed with "CIRCULAR+"
11
+ // if (seen.has(value)) {
12
+ // return `CIRCULAR${key}`;
13
+ // }
14
+ // seen.set(value, key); // Mark the object as visited with its name
15
+ // }
16
+ return value;
17
+ }
18
+ // Convert the object to a string and then parse it back
19
+ // This will trigger the replacer function to handle circular references
20
+ const jsonString = JSON.stringify(obj, replacer);
21
+ return JSON.parse(jsonString);
22
+ }
23
+ /**
24
+ * Recursively converts dot-separated keys in an object to nested objects.
25
+ *
26
+ * @param {any} obj - The object to process.
27
+ * @returns {any} - The object with all dot-separated keys converted to nested objects.
28
+ * @example
29
+ * Input:
30
+ * {
31
+ * "http.method": "GET",
32
+ * "http.url": "http://example.com",
33
+ * "nested.obj.key": "value"
34
+ * }
35
+ * Output:
36
+ * {
37
+ * "http": {
38
+ * "method": "GET",
39
+ * "url": "http://example.com"
40
+ * },
41
+ * "nested": {
42
+ * "obj": {
43
+ * "key": "value"
44
+ * }
45
+ * }
46
+ * }
47
+ */
48
+ export function applyNesting(obj) {
49
+ if (Array.isArray(obj)) {
50
+ return obj.map(item => applyNesting(item));
51
+ }
52
+ else if (typeof obj === 'object' && obj !== null) {
53
+ const result = {};
54
+ for (const key in obj) {
55
+ const value = applyNesting(obj[key]);
56
+ const keys = key.split('.');
57
+ let temp = result;
58
+ for (let i = 0; i < keys.length; i++) {
59
+ const currentKey = keys[i];
60
+ if (i === keys.length - 1) {
61
+ temp[currentKey] = value;
62
+ }
63
+ else {
64
+ if (!temp[currentKey]) {
65
+ temp[currentKey] = {};
66
+ }
67
+ temp = temp[currentKey];
68
+ }
69
+ }
70
+ }
71
+ return result;
72
+ }
73
+ else {
74
+ return obj;
75
+ }
76
+ }
@@ -0,0 +1,163 @@
1
+ import { resourceFromAttributes } from "@opentelemetry/resources";
2
+ import logger from "../../utils/logger.js";
3
+ export class Enabler {
4
+ constructor(enabled) {
5
+ this._enabled = true;
6
+ if (typeof enabled === 'boolean') {
7
+ this._enabled = enabled;
8
+ }
9
+ }
10
+ setEnabledValue(value) {
11
+ this._enabled = value;
12
+ }
13
+ enable() {
14
+ this._enabled = true;
15
+ }
16
+ disable() {
17
+ this._enabled = false;
18
+ }
19
+ isEnabled() {
20
+ return this._enabled;
21
+ }
22
+ toggle() {
23
+ this._enabled = !this._enabled;
24
+ }
25
+ }
26
+ /*
27
+ * Classes for enabler exporters, allowing enable/disable toggling of exporters at runtime.
28
+ *
29
+ * This class is designed to work for trace and log exporters, where multiple exporters can be active simultaneously
30
+ * (e.g., via a MultiExporter and one processor). By toggling the exporter, you can dynamically enable or
31
+ * disable the export operation without removing the exporter from the pipeline.
32
+ *
33
+ * For metrics, the OpenTelemetry model is different: there are two types of readers. PullMetricExporters do not have
34
+ * an exporter instance, and PushMetricExporters do, but only allow a single exporter per reader (with optional
35
+ * `selectAggregationTemporality` and `selectAggregation` attributes). Therefore, multiple exporters per reader are not
36
+ * supported for metrics.
37
+ *
38
+ * For metrics readers, a separate `DeactivableReader` class is recommended, which is even more efficient: when
39
+ * disabled, the reader's `collect` method is not called at all, saving additional resources compared to disabling the exporter.
40
+ *
41
+ */
42
+ export class EnablerSpanExporter extends Enabler {
43
+ constructor(exporter) {
44
+ super();
45
+ this.exporter = exporter;
46
+ }
47
+ export(spans, resultCallback) {
48
+ if (this.isEnabled()) {
49
+ this.exporter.export(spans, resultCallback);
50
+ }
51
+ }
52
+ shutdown() {
53
+ return this.exporter.shutdown();
54
+ }
55
+ forceFlush() {
56
+ if (typeof this.exporter.forceFlush === 'function') {
57
+ return this.exporter.forceFlush();
58
+ }
59
+ return Promise.resolve();
60
+ }
61
+ }
62
+ export class EnablerLogExporter extends Enabler {
63
+ constructor(exporter) {
64
+ super();
65
+ this.exporter = exporter;
66
+ }
67
+ export(logs, resultCallback) {
68
+ if (this.isEnabled()) {
69
+ this.exporter.export(logs, resultCallback);
70
+ }
71
+ }
72
+ shutdown() {
73
+ return this.exporter.shutdown();
74
+ }
75
+ }
76
+ class EnablerMultiExporter extends Enabler {
77
+ constructor(exporters) {
78
+ super();
79
+ this._exporters = [];
80
+ if (exporters && Array.isArray(exporters)) {
81
+ this._exporters = exporters;
82
+ }
83
+ }
84
+ addExporters(exporter) {
85
+ if (!this._exporters) {
86
+ this._exporters = [];
87
+ }
88
+ if (Array.isArray(exporter)) {
89
+ this._exporters.push(...exporter);
90
+ }
91
+ else {
92
+ this._exporters.push(exporter);
93
+ }
94
+ }
95
+ }
96
+ export class EnablerMultiSpanExporter extends EnablerMultiExporter {
97
+ export(spans, resultCallback) {
98
+ logger.debug(`EnablerMultiSpanExporter.export called with ${spans.length} spans, exporters: ${this._exporters?.length ?? 0}, enabled: ${this.isEnabled()}`);
99
+ if (this.isEnabled() && this._exporters) {
100
+ this._exporters.forEach((exporter) => exporter.export(spans, resultCallback));
101
+ }
102
+ }
103
+ async shutdown() {
104
+ if (this._exporters) {
105
+ await Promise.all(this._exporters.map((exporter) => exporter.shutdown()));
106
+ }
107
+ return;
108
+ }
109
+ }
110
+ export class EnablerMultiLogExporter extends EnablerMultiExporter {
111
+ export(logs, resultCallback) {
112
+ if (this.isEnabled() && this._exporters) {
113
+ this._exporters.forEach((exporter) => exporter.export(logs, resultCallback));
114
+ }
115
+ }
116
+ async shutdown() {
117
+ if (this._exporters) {
118
+ await Promise.all(this._exporters.map((exporter) => exporter.shutdown()));
119
+ }
120
+ return;
121
+ }
122
+ }
123
+ export class EnablerMetricReader extends Enabler {
124
+ constructor(reader) {
125
+ super();
126
+ this.reader = reader;
127
+ }
128
+ forceFlush(options) {
129
+ if (this.isEnabled()) {
130
+ return this.reader.forceFlush(options);
131
+ }
132
+ return Promise.resolve();
133
+ }
134
+ setMetricProducer(metricProducer) {
135
+ if (this.isEnabled()) {
136
+ this.reader.setMetricProducer(metricProducer);
137
+ }
138
+ }
139
+ selectAggregation(instrumentType) {
140
+ return this.reader.selectAggregation(instrumentType);
141
+ }
142
+ selectAggregationTemporality(instrumentType) {
143
+ return this.reader.selectAggregationTemporality(instrumentType);
144
+ }
145
+ selectCardinalityLimit(instrumentType) {
146
+ return this.reader.selectCardinalityLimit(instrumentType);
147
+ }
148
+ async collect(options) {
149
+ if (this.isEnabled()) {
150
+ return await this.reader.collect(options);
151
+ }
152
+ return {
153
+ resourceMetrics: {
154
+ resource: resourceFromAttributes({}),
155
+ scopeMetrics: [],
156
+ },
157
+ errors: [],
158
+ };
159
+ }
160
+ shutdown(options) {
161
+ return this.reader.shutdown(options);
162
+ }
163
+ }
@@ -0,0 +1,71 @@
1
+ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
2
+ import logger from '../utils/logger.js';
3
+ import { registerInstrumentations } from '@opentelemetry/instrumentation';
4
+ import { dynamicMultiLogProcessor, dynamicMultiSpanProcessor, oasTelemetryResource } from './telemetryRegistry.js';
5
+ import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
6
+ import { SeverityNumber } from '@opentelemetry/api-logs';
7
+ import { LoggerProvider } from '@opentelemetry/sdk-logs';
8
+ import { bootEnvVariables } from '../config/bootConfig.js';
9
+ // THIS INSTRUMENTATIONS NEED TO BE LOADED BEFORE ANYTHING ELSE
10
+ // They use monkey-patching to instrument the HTTP server and client.
11
+ // THIS FILE MUST BE CALLED BEFORE ANYTHING ELSE
12
+ if (bootEnvVariables.OASTLM_BOOT_MODULE_DISABLED) {
13
+ logger.info('🚫 OASTLM module is disabled, Providers not initialized.');
14
+ }
15
+ else {
16
+ logger.info('🚀 Initializing Open Telemetry');
17
+ initializeTraces();
18
+ initializeMetrics();
19
+ initializeLogs();
20
+ registerInstrumentations({
21
+ instrumentations: [
22
+ new HttpInstrumentation(),
23
+ // new ExpressInstrumentation(),
24
+ ],
25
+ });
26
+ }
27
+ function initializeTraces() {
28
+ logger.info('📊 Initializing TracerProvider');
29
+ const tracerProvider = new NodeTracerProvider({
30
+ resource: oasTelemetryResource,
31
+ spanProcessors: [dynamicMultiSpanProcessor]
32
+ });
33
+ tracerProvider.register();
34
+ }
35
+ function initializeLogs() {
36
+ logger.info('📜 Initializing LoggerProvider');
37
+ // Create and configure LoggerProvider
38
+ const loggerProvider = new LoggerProvider({
39
+ resource: oasTelemetryResource,
40
+ processors: [dynamicMultiLogProcessor]
41
+ });
42
+ // Get a logger instance
43
+ const loggerInstance = loggerProvider.getLogger('oas-telemetry'); // Use loggerProvider to get the logger
44
+ // Override console methods to emit logs via OpenTelemetry, like an instrumentation
45
+ const originalConsoleMethods = {
46
+ log: console.log,
47
+ warn: console.warn,
48
+ error: console.error,
49
+ info: console.info,
50
+ debug: console.debug,
51
+ };
52
+ Object.keys(originalConsoleMethods).forEach((method) => {
53
+ // @ts-expect-error yes
54
+ console[method] = (...args) => {
55
+ loggerInstance.emit({
56
+ severityNumber: SeverityNumber[method.toUpperCase()] || SeverityNumber.INFO,
57
+ severityText: method.toUpperCase(),
58
+ body: args.join(' '),
59
+ attributes: { 'source.source': `console.${method}` },
60
+ });
61
+ // @ts-expect-error yes
62
+ originalConsoleMethods[method](...args);
63
+ };
64
+ });
65
+ }
66
+ function initializeMetrics() {
67
+ logger.info('📈 Initializing MeterProvider');
68
+ // WARN: This is a custom provider that allows adding readers dynamically at runtime.
69
+ // WARN: Default PeriodicExportingMetricReader is added post initialization (see telemetryConfigurator.ts)
70
+ // The in memory exporter is added by default to that reader. More readers are allowed to be added dynamically
71
+ }
@@ -0,0 +1,74 @@
1
+ import { BatchSpanProcessor, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
2
+ import { dynamicMultiLogProcessor, dynamicMultiSpanProcessor, inMemoryDbLogExporter, inMemoryDbMetricExporter, inMemoryDbSpanExporter, multiLogExporter, multiSpanExporter, oasTelemetryResource, pluginLogExporter, pluginMetricExporter, pluginSpanExporter } from './telemetryRegistry.js';
3
+ import logger from '../utils/logger.js';
4
+ import { BatchLogRecordProcessor, SimpleLogRecordProcessor } from '@opentelemetry/sdk-logs';
5
+ import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
6
+ import { HostMetrics } from '@opentelemetry/host-metrics';
7
+ import { bootEnvVariables } from '../config/bootConfig.js';
8
+ export const configureTelemetry = (oasTlmConfig) => {
9
+ configureTraces(oasTlmConfig);
10
+ configureMetrics(oasTlmConfig);
11
+ configureLogs(oasTlmConfig);
12
+ logger.info("✅ Telemetry configured successfully. All exporters are ready");
13
+ };
14
+ function configureTraces(oasTlmConfig) {
15
+ // TRACES CONFIGURATION
16
+ // [OT]Provider -> [OT]SpanProcessor(multiSpan) -> n Processors(eg mainProcessor, extra) -> 1 SpanExporter
17
+ inMemoryDbSpanExporter.baseUrl = oasTlmConfig.general.baseUrl; // TODO this will be done with filters
18
+ inMemoryDbSpanExporter.retentionTimeInSeconds = oasTlmConfig.traces.memoryExporter.retentionTimeSeconds;
19
+ inMemoryDbSpanExporter.setEnabledValue(oasTlmConfig.traces.memoryExporter.enabled);
20
+ pluginSpanExporter.setEnabledValue(oasTlmConfig.plugins.enabled);
21
+ const mainExporter = multiSpanExporter;
22
+ let mainProcessor = new BatchSpanProcessor(mainExporter);
23
+ if (bootEnvVariables.OASTLM_BOOT_ENV !== 'production') {
24
+ logger.info('Not in production, using SimpleSpanProcessor for traces');
25
+ mainProcessor = new SimpleSpanProcessor(mainExporter);
26
+ }
27
+ mainExporter.addExporters(inMemoryDbSpanExporter); // Main exporter have at least the in-memory exporter used by the traces controller
28
+ mainExporter.addExporters(pluginSpanExporter);
29
+ mainExporter.addExporters(oasTlmConfig.traces.extraExporters);
30
+ dynamicMultiSpanProcessor.addProcessors(mainProcessor);
31
+ dynamicMultiSpanProcessor.addProcessors(oasTlmConfig.traces.extraProcessors);
32
+ }
33
+ function configureLogs(oasTlmConfig) {
34
+ // LOGS CONFIGURATION
35
+ // [OT]LoggerProvider -> [OT]LogRecordProcessor(multiLogProcessor) -> n Processors(eg mainProcessor, extra) -> 1 LogExporter
36
+ inMemoryDbLogExporter.setEnabledValue(oasTlmConfig.logs.memoryExporter.enabled);
37
+ inMemoryDbLogExporter.retentionTimeInSeconds = oasTlmConfig.logs.memoryExporter.retentionTimeSeconds;
38
+ const mainExporter = multiLogExporter;
39
+ let mainProcessor = new BatchLogRecordProcessor(mainExporter);
40
+ if (bootEnvVariables.OASTLM_BOOT_ENV !== 'production') {
41
+ logger.info('Not in production, using SimpleLogRecordProcessor for logs');
42
+ mainProcessor = new SimpleLogRecordProcessor(mainExporter);
43
+ }
44
+ mainExporter.addExporters(inMemoryDbLogExporter); // Main exporter have at least the in-memory exporter used by the logs controller
45
+ mainExporter.addExporters(oasTlmConfig.logs.extraExporters);
46
+ mainExporter.addExporters(pluginLogExporter); // Allow logs to be sent to plugins too
47
+ dynamicMultiLogProcessor.addProcessors(mainProcessor);
48
+ dynamicMultiLogProcessor.addProcessors(oasTlmConfig.logs.extraProcessors);
49
+ }
50
+ function configureMetrics(oasTlmConfig) {
51
+ // METRICS CONFIGURATION
52
+ // [CUSTOM]MeterProvider -> n [OTel]MetricReader -> (0-1) MetricExporter (only if push-based reader)
53
+ inMemoryDbMetricExporter.setEnabledValue(oasTlmConfig.metrics.memoryExporter.enabled);
54
+ inMemoryDbMetricExporter.retentionTimeInSeconds = oasTlmConfig.metrics.memoryExporter.retentionTimeSeconds;
55
+ const mainReader = new PeriodicExportingMetricReader({
56
+ exporter: inMemoryDbMetricExporter,
57
+ exportIntervalMillis: oasTlmConfig.metrics.mainMetricReaderOptions.exportIntervalMillis,
58
+ metricProducers: oasTlmConfig.metrics.mainMetricReaderOptions.metricProducers
59
+ });
60
+ const pluginReader = new PeriodicExportingMetricReader({
61
+ exporter: pluginMetricExporter,
62
+ exportIntervalMillis: oasTlmConfig.metrics.mainMetricReaderOptions.exportIntervalMillis,
63
+ metricProducers: oasTlmConfig.metrics.mainMetricReaderOptions.metricProducers
64
+ });
65
+ const meterProvider = new MeterProvider({
66
+ resource: oasTelemetryResource,
67
+ readers: [mainReader, pluginReader, ...oasTlmConfig.metrics.extraReaders],
68
+ views: oasTlmConfig.metrics.extraViews || []
69
+ });
70
+ // TODO maybe hostMetrics are too much, consider using only a subset of them.
71
+ const hostMetrics = new HostMetrics({ meterProvider: meterProvider });
72
+ // AFTER adding all readers, start the instrumentations
73
+ hostMetrics.start();
74
+ }
@@ -0,0 +1,34 @@
1
+ import { InMemoryDbSpanExporter } from "./custom-implementations/exporters/InMemoryDbSpanExporter.js";
2
+ import { EnablerMultiLogExporter, EnablerMultiSpanExporter } from "./custom-implementations/wrappers.js";
3
+ import { InMemoryDbLogExporter } from "./custom-implementations/exporters/InMemoryDbLogExporter.js";
4
+ import { InMemoryDbMetricExporter } from "./custom-implementations/exporters/InMemoryDbMetricExporter.js";
5
+ import { DynamicMultiSpanProcessor } from "./custom-implementations/processors/dynamicMultiSpanProcessor.js";
6
+ import { DynamicMultiLogRecordProcessor } from "./custom-implementations/processors/dynamicMultiLogProcessor.js";
7
+ import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
8
+ import { resourceFromAttributes } from "@opentelemetry/resources";
9
+ import { PluginSpanExporter } from "./custom-implementations/exporters/PluginSpanExporter.js";
10
+ import { PluginLogExporter } from "./custom-implementations/exporters/PluginLogExporter.js";
11
+ import { PluginMetricExporter } from "./custom-implementations/exporters/PluginMetricExporter.js";
12
+ // GLOBAL REGISTRY of telemetry components, used by SDKs and controllers.
13
+ export const oasTelemetryResource = resourceFromAttributes({
14
+ [ATTR_SERVICE_NAME]: 'oas-telemetry-service'
15
+ });
16
+ // TRACES -------------------------------------------------------------------------------------
17
+ // This is the main exporter for oas-telemetry spans. (Used by the traces controller)
18
+ export const inMemoryDbSpanExporter = new InMemoryDbSpanExporter();
19
+ // This allows to add more exporter in the future without changing the code
20
+ export const multiSpanExporter = new EnablerMultiSpanExporter();
21
+ // This allows the addition of more processors at runtime
22
+ export const dynamicMultiSpanProcessor = new DynamicMultiSpanProcessor();
23
+ export const pluginSpanExporter = new PluginSpanExporter(); // This exporter sends spans to the plugin module.
24
+ // LOGS ----------------------------------------------------------------------------------------
25
+ export const inMemoryDbLogExporter = new InMemoryDbLogExporter();
26
+ export const multiLogExporter = new EnablerMultiLogExporter();
27
+ export const dynamicMultiLogProcessor = new DynamicMultiLogRecordProcessor();
28
+ export const pluginLogExporter = new PluginLogExporter(); // This exporter sends logs to the plugin module.
29
+ // METRICS -------------------------------------------------------------------------------------
30
+ // Metrics follow a different pattern in OpenTelemetry
31
+ export const inMemoryDbMetricExporter = new InMemoryDbMetricExporter();
32
+ export const pluginMetricExporter = new PluginMetricExporter(); // This exporter sends metrics to the plugin module.
33
+ // Readers and their exporters cannot be grouped together in a MultiReader or similar construct
34
+ // due to differences in aggregation temporality and aggregation selection.
@@ -1,68 +1,86 @@
1
1
  import OpenAI from 'openai';
2
- import dotenv from 'dotenv';
3
2
  import { tools, availableTools } from './tools.js';
4
3
  import logger from '../utils/logger.js';
5
- dotenv.config();
6
- let openai;
7
- try {
8
- openai = new OpenAI({
9
- apiKey: process.env.OASTLM_AI_OPENAI_API_KEY,
10
- dangerouslyAllowBrowser: true,
11
- });
12
- }
13
- catch {
14
- openai = null;
15
- }
16
- const messages = [
17
- {
18
- role: "assistant",
19
- content: "You are a helpful telemetry assistant. Only use the functions you have been provided with. If the question is not related to the functions, respond with 'I cannot help with that.'. If you need to call to other agents, do so using the tools provided."
20
- },
21
- ];
22
- async function agent(userInput) {
23
- messages.push({
24
- role: "user",
25
- content: userInput,
26
- });
27
- for (let i = 0; i < 5; i++) {
28
- const response = await (openai?.chat?.completions?.create?.({
29
- model: process.env.OASTLM_AI_OPENAI_MODEL_NAME || "gpt-4o-mini",
30
- messages: messages,
31
- tools: tools,
32
- }));
33
- const { finish_reason, message } = response.choices[0];
34
- if (finish_reason === "tool_calls" && message.tool_calls) {
35
- logger.debug("Tool calls detected:", message.tool_calls);
36
- const results = [];
37
- for (const toolCall of message.tool_calls) {
38
- const functionName = toolCall.function.name;
39
- const functionToCall = availableTools[functionName];
40
- const functionArgs = JSON.parse(toolCall.function.arguments);
41
- const functionArgsArr = Object.values(functionArgs);
42
- // @ts-expect-error yes
43
- // eslint-disable-next-line prefer-spread
44
- const functionResponse = await functionToCall.apply(null, functionArgsArr);
45
- results.push({
46
- name: functionName,
47
- response: functionResponse,
48
- });
49
- }
50
- const resultMessage = results.map(({ name, response }) => `Result from "${name}":\n${JSON.stringify(response, null, 2)}`).join("\n\n");
4
+ export function getAgent(oasTlmConfig) {
5
+ let openai;
6
+ try {
7
+ if (!oasTlmConfig.ai.openAIKey) {
8
+ openai = null;
9
+ }
10
+ else {
11
+ openai = new OpenAI({
12
+ apiKey: oasTlmConfig.ai.openAIKey ?? undefined,
13
+ dangerouslyAllowBrowser: true,
14
+ });
15
+ }
16
+ }
17
+ catch {
18
+ openai = null;
19
+ }
20
+ const messages = [
21
+ {
22
+ role: "assistant",
23
+ content: "You are a helpful telemetry assistant. Only use the functions you have been provided with. If the question is not related to the functions, respond with 'I cannot help with that.'. If you need to call to other agents, do so using the tools provided."
24
+ },
25
+ ];
26
+ // Add extra context prompts if provided
27
+ if (oasTlmConfig.ai.extraContextPrompts) {
28
+ for (const prompt of oasTlmConfig.ai.extraContextPrompts) {
51
29
  messages.push({
52
- role: "function",
53
- name: "multiple_tool_calls",
54
- content: resultMessage,
30
+ role: "system",
31
+ content: prompt,
55
32
  });
56
33
  }
57
- else if (finish_reason === "stop") {
58
- messages.push(message);
59
- return message;
34
+ }
35
+ async function agent(userInput) {
36
+ if (!openai) {
37
+ logger.error("OpenAI client is not initialized. Please check your OpenAI API key.");
38
+ return { content: "OpenAI client is not initialized. Please check your OpenAI API key." };
60
39
  }
40
+ messages.push({
41
+ role: "user",
42
+ content: userInput,
43
+ });
44
+ for (let i = 0; i < 5; i++) {
45
+ const response = await (openai?.chat?.completions?.create?.({
46
+ model: oasTlmConfig.ai.openAIModel,
47
+ messages: messages,
48
+ tools: tools,
49
+ }));
50
+ const { finish_reason, message } = response.choices[0];
51
+ if (finish_reason === "tool_calls" && message.tool_calls) {
52
+ logger.debug("Tool calls detected:", message.tool_calls);
53
+ const results = [];
54
+ for (const toolCall of message.tool_calls) {
55
+ const functionName = toolCall.function.name;
56
+ const functionToCall = availableTools[functionName];
57
+ const functionArgs = JSON.parse(toolCall.function.arguments);
58
+ const functionArgsArr = Object.values(functionArgs);
59
+ // @ts-expect-error yes
60
+ // eslint-disable-next-line prefer-spread
61
+ const functionResponse = await functionToCall.apply(null, functionArgsArr);
62
+ results.push({
63
+ name: functionName,
64
+ response: functionResponse,
65
+ });
66
+ }
67
+ const resultMessage = results.map(({ name, response }) => `Result from "${name}":\n${JSON.stringify(response, null, 2)}`).join("\n\n");
68
+ messages.push({
69
+ role: "function",
70
+ name: "multiple_tool_calls",
71
+ content: resultMessage,
72
+ });
73
+ }
74
+ else if (finish_reason === "stop") {
75
+ messages.push(message);
76
+ return message;
77
+ }
78
+ }
79
+ return { content: "Se alcanzó el número máximo de iteraciones sin una respuesta adecuada. Intenta con una consulta más específica." };
61
80
  }
62
- return { content: "Se alcanzó el número máximo de iteraciones sin una respuesta adecuada. Intenta con una consulta más específica." };
63
- }
64
- export async function getAgentResponse(question) {
65
- const response = await agent(question);
66
- logger.debug("Response from agent:", response);
67
- return response.content;
81
+ return async function getAgentResponse(question) {
82
+ const response = await agent(question);
83
+ logger.debug("Response from agent:", response);
84
+ return response.content;
85
+ };
68
86
  }
@@ -1,11 +1,12 @@
1
- import { getAgentResponse } from './agent.js';
1
+ import { getAgent } from './agent.js';
2
2
  import { setKnownMicroservices, getKnownMicroservices } from './knownMicroservices.js';
3
3
  import logger from '../utils/logger.js';
4
- export const answerQuestion = async (req, res) => {
4
+ export const answerQuestion = (oasTlmConfig) => async (req, res) => {
5
5
  try {
6
6
  const { question } = req.body;
7
7
  if (!question)
8
8
  res.status(400).json({ error: 'Missing question' });
9
+ const getAgentResponse = getAgent(oasTlmConfig);
9
10
  const answer = await getAgentResponse(question);
10
11
  res.json({ answer });
11
12
  }
@@ -14,7 +15,7 @@ export const answerQuestion = async (req, res) => {
14
15
  res.status(500).json({ error: 'Internal error' });
15
16
  }
16
17
  };
17
- export const setKnownMicroservicesHandler = (req, res) => {
18
+ export const setKnownMicroservicesHandler = () => (req, res) => {
18
19
  try {
19
20
  const { microservices } = req.body;
20
21
  if (!Array.isArray(microservices)) {
@@ -33,7 +34,7 @@ export const setKnownMicroservicesHandler = (req, res) => {
33
34
  res.status(500).json({ error: 'Internal error' });
34
35
  }
35
36
  };
36
- export const getKnownMicroservicesHandler = (req, res) => {
37
+ export const getKnownMicroservicesHandler = () => (req, res) => {
37
38
  try {
38
39
  const microservices = getKnownMicroservices();
39
40
  res.json({ knownMicroservices: microservices });
@@ -1,7 +1,9 @@
1
1
  import { Router } from 'express';
2
2
  import { answerQuestion, setKnownMicroservicesHandler, getKnownMicroservicesHandler } from './aiController.js';
3
- export const aiRoutes = Router();
4
- aiRoutes.post('/chat', answerQuestion);
5
- aiRoutes.post('/microservices', setKnownMicroservicesHandler); // New route for configuring microservices
6
- aiRoutes.get('/microservices', getKnownMicroservicesHandler); // Route to retrieve the list of known microservices
7
- export default aiRoutes;
3
+ export const getAIRoutes = (oasTlmConfig) => {
4
+ const router = Router();
5
+ router.post('/chat', answerQuestion(oasTlmConfig));
6
+ router.post('/microservices', setKnownMicroservicesHandler());
7
+ router.get('/microservices', getKnownMicroservicesHandler());
8
+ return router;
9
+ };