@oas-tools/oas-telemetry 0.7.0-alpha.5 → 0.7.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/.env.example +2 -0
- package/README.md +35 -17
- package/dist/cjs/config/bootConfig.cjs +3 -1
- package/dist/cjs/docs/openapi.yaml +1399 -0
- package/dist/cjs/routesManager.cjs +1 -1
- package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.cjs +43 -13
- package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.cjs +10 -2
- package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.cjs +21 -16
- package/dist/cjs/telemetry/initializeTelemetry.cjs +39 -15
- package/dist/cjs/telemetry/telemetryConfigurator.cjs +6 -9
- package/dist/cjs/telemetry/telemetryRegistry.cjs +11 -8
- package/dist/cjs/tlm-ai/agent.cjs +54 -84
- package/dist/cjs/tlm-ai/aiController.cjs +69 -47
- package/dist/cjs/tlm-ai/aiRoutes.cjs +10 -3
- package/dist/cjs/tlm-ai/aiService.cjs +109 -0
- package/dist/cjs/tlm-ai/tools.cjs +30 -268
- package/dist/cjs/tlm-auth/authController.cjs +9 -9
- package/dist/cjs/tlm-auth/authMiddleware.cjs +1 -1
- package/dist/cjs/tlm-log/logController.cjs +30 -36
- package/dist/cjs/tlm-log/logRoutes.cjs +3 -2
- package/dist/cjs/tlm-metric/metricsController.cjs +15 -8
- package/dist/cjs/tlm-metric/metricsRoutes.cjs +2 -1
- package/dist/cjs/tlm-plugin/pluginController.cjs +11 -1
- package/dist/cjs/tlm-plugin/pluginProcess.cjs +4 -2
- package/dist/cjs/tlm-plugin/pluginService.cjs +3 -0
- package/dist/cjs/tlm-trace/traceController.cjs +16 -9
- package/dist/cjs/tlm-trace/traceRoutes.cjs +2 -1
- package/dist/cjs/tlm-util/utilController.cjs +23 -2
- package/dist/cjs/tlm-util/utilRoutes.cjs +44 -5
- package/dist/cjs/utils/logger.cjs +35 -13
- package/dist/esm/config/bootConfig.js +2 -0
- package/dist/esm/docs/openapi.yaml +1399 -0
- package/dist/esm/routesManager.js +1 -1
- package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.js +32 -11
- package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.js +10 -2
- package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.js +20 -13
- package/dist/esm/telemetry/initializeTelemetry.js +22 -14
- package/dist/esm/telemetry/telemetryConfigurator.js +7 -10
- package/dist/esm/telemetry/telemetryRegistry.js +10 -7
- package/dist/esm/tlm-ai/agent.js +37 -78
- package/dist/esm/tlm-ai/aiController.js +56 -39
- package/dist/esm/tlm-ai/aiRoutes.js +11 -4
- package/dist/esm/tlm-ai/aiService.js +94 -0
- package/dist/esm/tlm-ai/tools.js +29 -255
- package/dist/esm/tlm-auth/authController.js +8 -8
- package/dist/esm/tlm-auth/authMiddleware.js +1 -1
- package/dist/esm/tlm-log/logController.js +26 -28
- package/dist/esm/tlm-log/logRoutes.js +4 -3
- package/dist/esm/tlm-metric/metricsController.js +10 -6
- package/dist/esm/tlm-metric/metricsRoutes.js +3 -2
- package/dist/esm/tlm-plugin/pluginController.js +2 -1
- package/dist/esm/tlm-plugin/pluginProcess.js +4 -2
- package/dist/esm/tlm-plugin/pluginService.js +4 -0
- package/dist/esm/tlm-trace/traceController.js +11 -7
- package/dist/esm/tlm-trace/traceRoutes.js +3 -2
- package/dist/esm/tlm-util/utilController.js +22 -0
- package/dist/esm/tlm-util/utilRoutes.js +40 -5
- package/dist/esm/utils/logger.js +35 -12
- package/dist/types/config/bootConfig.d.ts +1 -0
- package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.d.ts +7 -1
- package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.d.ts +1 -0
- package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.d.ts +1 -0
- package/dist/types/telemetry/telemetryRegistry.d.ts +22 -6
- package/dist/types/tlm-ai/agent.d.ts +2 -2
- package/dist/types/tlm-ai/aiController.d.ts +5 -4
- package/dist/types/tlm-ai/aiRoutes.d.ts +1 -1
- package/dist/types/tlm-ai/aiService.d.ts +38 -0
- package/dist/types/tlm-ai/tools.d.ts +5 -14
- package/dist/types/tlm-log/logController.d.ts +2 -2
- package/dist/types/tlm-metric/metricsController.d.ts +2 -1
- package/dist/types/tlm-plugin/pluginService.d.ts +2 -0
- package/dist/types/tlm-trace/traceController.d.ts +2 -1
- package/dist/types/tlm-util/utilController.d.ts +1 -0
- package/dist/types/utils/logger.d.ts +5 -5
- package/dist/ui/assets/ApiDocsPage-C_VVPPHa.js +16 -0
- package/dist/ui/assets/CollapsibleCard-B3KR_8mL.js +1 -0
- package/dist/ui/assets/DevToolsPage-OyZcDcmw.js +1 -0
- package/dist/ui/assets/LandingPage-CppFBA6K.js +6 -0
- package/dist/ui/assets/LogsPage-9Fq8GArS.js +26 -0
- package/dist/ui/assets/NotFoundPage-B3quk3P1.js +1 -0
- package/dist/ui/assets/PluginCreatePage-X_aCH4t4.js +50 -0
- package/dist/ui/assets/PluginPage-DMDSihrZ.js +27 -0
- package/dist/ui/assets/alert-jQ9HCPIf.js +1133 -0
- package/dist/ui/assets/badge-CNq0-mH5.js +1 -0
- package/dist/ui/assets/card-DFAwwhN3.js +1 -0
- package/dist/ui/assets/chevron-down-CPsvsmqj.js +6 -0
- package/dist/ui/assets/chevron-up-Df9jMo1X.js +6 -0
- package/dist/ui/assets/circle-alert-DOPQPvU8.js +6 -0
- package/dist/ui/assets/index-BkD6DijD.js +15 -0
- package/dist/ui/assets/index-CERGVYZK.js +292 -0
- package/dist/ui/assets/index-CSIPf9qw.css +1 -0
- package/dist/ui/assets/input-Dzvg_ZEZ.js +1 -0
- package/dist/ui/assets/label-DuVnkZ4q.js +1 -0
- package/dist/ui/assets/loader-circle-CrvlRy5o.js +6 -0
- package/dist/ui/assets/loginPage-qa4V-B70.js +6 -0
- package/dist/ui/assets/select-DhS8YUtJ.js +1 -0
- package/dist/ui/assets/separator-isK4chBP.js +6 -0
- package/dist/ui/assets/severityOptions-O38dSOfk.js +11 -0
- package/dist/ui/assets/switch-Z3mImG9n.js +1 -0
- package/dist/ui/assets/tabs-_77MUUQe.js +16 -0
- package/dist/ui/assets/upload-C1LT4Gkb.js +16 -0
- package/dist/ui/assets/utilService-DNyqzwj0.js +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +17 -6
- package/dist/ui/assets/index-Bgd7fFFH.js +0 -1743
- package/dist/ui/assets/index-Cz3N1n1Q.css +0 -1
|
@@ -46,6 +46,7 @@ export const configureRoutes = (router, oasTlmConfig) => {
|
|
|
46
46
|
});
|
|
47
47
|
// WARNING: This path must be the same as the one used in the UI package App.tsx "oas-telemetry-ui"
|
|
48
48
|
telemetryRouter.use("/oas-telemetry-ui", getUIRoutes());
|
|
49
|
+
telemetryRouter.use("/utils", getUtilsRoutes(oasTlmConfig));
|
|
49
50
|
// Auth routes must be registered. If authentication is not enabled, all requests will be allowed.
|
|
50
51
|
// Frontend will use these endpoints;
|
|
51
52
|
telemetryRouter.use(cookieParser());
|
|
@@ -58,7 +59,6 @@ export const configureRoutes = (router, oasTlmConfig) => {
|
|
|
58
59
|
if (oasTlmConfig.ai.openAIKey) {
|
|
59
60
|
telemetryRouter.use("/ai", getAIRoutes(oasTlmConfig));
|
|
60
61
|
}
|
|
61
|
-
telemetryRouter.use("/utils", getUtilsRoutes(oasTlmConfig));
|
|
62
62
|
telemetryRouter.use("/plugins", getPluginRoutes());
|
|
63
63
|
// Mount the telemetryRouter under telemetryBaseUrl
|
|
64
64
|
router.use(telemetryBaseUrl, telemetryRouter);
|
|
@@ -5,6 +5,7 @@ import MiniSearch from 'minisearch';
|
|
|
5
5
|
import { applyNesting, removeCircularRefs } from '../utils/circular.js';
|
|
6
6
|
import { Enabler } from '../wrappers.js';
|
|
7
7
|
import logger from '../../../utils/logger.js';
|
|
8
|
+
import { pluginService } from '../../../tlm-plugin/pluginService.js';
|
|
8
9
|
export class InMemoryDbLogExporter extends Enabler {
|
|
9
10
|
constructor(retentionTimeInSeconds = 3600) {
|
|
10
11
|
super();
|
|
@@ -30,10 +31,6 @@ export class InMemoryDbLogExporter extends Enabler {
|
|
|
30
31
|
* @param resultCallback
|
|
31
32
|
*/
|
|
32
33
|
export(logs, resultCallback) {
|
|
33
|
-
if (!this.isEnabled()) {
|
|
34
|
-
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
34
|
const logsToInsert = logs.map(logRecord => {
|
|
38
35
|
// Remove circular references first, then apply nesting, then export info
|
|
39
36
|
const formattedLog = this._formatLogRecord(logRecord);
|
|
@@ -41,7 +38,14 @@ export class InMemoryDbLogExporter extends Enabler {
|
|
|
41
38
|
const nestedLog = applyNesting(cleanedLog);
|
|
42
39
|
return nestedLog;
|
|
43
40
|
});
|
|
44
|
-
|
|
41
|
+
logsToInsert.forEach(log => {
|
|
42
|
+
pluginService.broadcastLog(log);
|
|
43
|
+
});
|
|
44
|
+
// ENABLED only affect storage not plugin broadcasting
|
|
45
|
+
if (this.isEnabled()) {
|
|
46
|
+
this._insertLogs(logsToInsert, resultCallback);
|
|
47
|
+
}
|
|
48
|
+
resultCallback({ code: ExportResultCode.SUCCESS });
|
|
45
49
|
}
|
|
46
50
|
reset() {
|
|
47
51
|
this._db = new Datastore();
|
|
@@ -58,15 +62,28 @@ export class InMemoryDbLogExporter extends Enabler {
|
|
|
58
62
|
this._db = null;
|
|
59
63
|
this._miniSearch = null;
|
|
60
64
|
}
|
|
61
|
-
find(
|
|
65
|
+
async find(findConfig) {
|
|
66
|
+
const { query, messageSearch, limit, sortOrder } = findConfig;
|
|
67
|
+
const finalQuery = { ...query };
|
|
68
|
+
const effectiveSortOrder = sortOrder || { timestamp: -1 };
|
|
62
69
|
if (messageSearch) {
|
|
63
|
-
const searchResults = this._miniSearch.search(messageSearch);
|
|
70
|
+
const searchResults = this._miniSearch.search(messageSearch, { prefix: true, fuzzy: 0.2 });
|
|
64
71
|
const ids = searchResults.map((result) => result._id);
|
|
65
72
|
logger.debug(`MiniSearch found ${ids.length} results for search term "${messageSearch}"`, { depth: 3 });
|
|
66
|
-
|
|
67
|
-
query._id = { $in: ids };
|
|
73
|
+
finalQuery._id = { $in: ids };
|
|
68
74
|
}
|
|
69
|
-
|
|
75
|
+
const docs = await new Promise((resolve, reject) => {
|
|
76
|
+
this._db.find(finalQuery)
|
|
77
|
+
.sort(effectiveSortOrder)
|
|
78
|
+
.limit(limit)
|
|
79
|
+
.exec((err, docs) => {
|
|
80
|
+
if (err)
|
|
81
|
+
reject(err);
|
|
82
|
+
else
|
|
83
|
+
resolve(docs);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
return docs;
|
|
70
87
|
}
|
|
71
88
|
insert(data, callback) {
|
|
72
89
|
this._insertLogs(data, (result) => {
|
|
@@ -100,7 +117,8 @@ export class InMemoryDbLogExporter extends Enabler {
|
|
|
100
117
|
attributes: logRecord.resource.attributes,
|
|
101
118
|
},
|
|
102
119
|
instrumentationScope: logRecord.instrumentationScope,
|
|
103
|
-
timestamp: hrTimeToMicroseconds(logRecord.hrTime)
|
|
120
|
+
timestamp: hrTimeToMicroseconds(logRecord.hrTime) ?? Date.now(),
|
|
121
|
+
observedTimestamp: hrTimeToMicroseconds(logRecord.hrTimeObserved) ?? Date.now(),
|
|
104
122
|
traceId: logRecord.spanContext?.traceId,
|
|
105
123
|
spanId: logRecord.spanContext?.spanId,
|
|
106
124
|
traceFlags: logRecord.spanContext?.traceFlags,
|
|
@@ -114,6 +132,9 @@ export class InMemoryDbLogExporter extends Enabler {
|
|
|
114
132
|
this._retentionTimeInSeconds = retentionTimeInSeconds;
|
|
115
133
|
logger.info(`InMemoryDbLogExporter retention time set to ${this._retentionTimeInSeconds} seconds`);
|
|
116
134
|
}
|
|
135
|
+
get retentionTimeInSeconds() {
|
|
136
|
+
return this._retentionTimeInSeconds;
|
|
137
|
+
}
|
|
117
138
|
_insertLogs(logsToInsert, resultCallback) {
|
|
118
139
|
this._db.insert(logsToInsert, (err, newDocs) => {
|
|
119
140
|
if (err) {
|
|
@@ -3,6 +3,7 @@ import dataStore from '@seald-io/nedb';
|
|
|
3
3
|
import { applyNesting } from '../utils/circular.js';
|
|
4
4
|
import { Enabler } from '../wrappers.js';
|
|
5
5
|
import logger from '../../../utils/logger.js';
|
|
6
|
+
import { pluginService } from '../../../tlm-plugin/pluginService.js';
|
|
6
7
|
export class InMemoryDbMetricExporter extends Enabler {
|
|
7
8
|
constructor(retentionTimeInSeconds = 3600) {
|
|
8
9
|
super();
|
|
@@ -13,9 +14,13 @@ export class InMemoryDbMetricExporter extends Enabler {
|
|
|
13
14
|
}
|
|
14
15
|
export(metrics, resultCallback) {
|
|
15
16
|
try {
|
|
17
|
+
const scopeMetrics = metrics?.scopeMetrics;
|
|
18
|
+
const cleanMetrics = applyNesting(scopeMetrics);
|
|
19
|
+
cleanMetrics.forEach((metric) => {
|
|
20
|
+
pluginService.broadcastMetric(metric);
|
|
21
|
+
});
|
|
22
|
+
// Insert only if exporter is enabled
|
|
16
23
|
if (this.isEnabled()) {
|
|
17
|
-
const scopeMetrics = metrics?.scopeMetrics;
|
|
18
|
-
const cleanMetrics = applyNesting(scopeMetrics);
|
|
19
24
|
this._metrics.insert(cleanMetrics, (err, _newDoc) => {
|
|
20
25
|
if (err) {
|
|
21
26
|
logger.error('Insertion Error:', err);
|
|
@@ -62,6 +67,9 @@ export class InMemoryDbMetricExporter extends Enabler {
|
|
|
62
67
|
this._retentionTimeInSeconds = retentionTimeInSeconds;
|
|
63
68
|
logger.info(`InMemoryDbMetricExporter retention time set to ${this._retentionTimeInSeconds} seconds`);
|
|
64
69
|
}
|
|
70
|
+
get retentionTimeInSeconds() {
|
|
71
|
+
return this._retentionTimeInSeconds;
|
|
72
|
+
}
|
|
65
73
|
_startCleanupJob() {
|
|
66
74
|
const interval = 1000;
|
|
67
75
|
setInterval(() => {
|
|
@@ -3,6 +3,7 @@ import dataStore from '@seald-io/nedb';
|
|
|
3
3
|
import logger from '../../../utils/logger.js';
|
|
4
4
|
import { applyNesting, removeCircularRefs } from '../utils/circular.js';
|
|
5
5
|
import { Enabler } from '../wrappers.js';
|
|
6
|
+
import { pluginService } from '../../../tlm-plugin/pluginService.js';
|
|
6
7
|
export class InMemoryDbSpanExporter extends Enabler {
|
|
7
8
|
constructor(retentionTimeInSeconds = 3600) {
|
|
8
9
|
super();
|
|
@@ -20,32 +21,38 @@ export class InMemoryDbSpanExporter extends Enabler {
|
|
|
20
21
|
this._retentionTimeInSeconds = retentionTimeInSeconds;
|
|
21
22
|
logger.info(`InMemoryDbSpanExporter retention time set to ${this._retentionTimeInSeconds} seconds`);
|
|
22
23
|
}
|
|
24
|
+
get retentionTimeInSeconds() {
|
|
25
|
+
return this._retentionTimeInSeconds;
|
|
26
|
+
}
|
|
23
27
|
export(readableSpans, resultCallback) {
|
|
24
28
|
logger.debug('InMemoryDbSpanExporter.export called with spans: ', readableSpans.length);
|
|
25
29
|
try {
|
|
26
|
-
if (!this.isEnabled()) {
|
|
27
|
-
logger.debug('InMemoryDbSpanExporter is not enabled. Skipping export.');
|
|
28
|
-
return resultCallback({ code: ExportResultCode.SUCCESS });
|
|
29
|
-
}
|
|
30
30
|
// Prepare spans to be inserted into the in-memory database (remove circular references and convert to nested objects)
|
|
31
31
|
const cleanSpans = readableSpans
|
|
32
32
|
.map(nestedSpan => removeCircularRefs(nestedSpan)) // to avoid JSON parsing error
|
|
33
33
|
.map(span => applyNesting(span)) // to avoid dot notation in keys (neDB does not support dot notation in keys)
|
|
34
34
|
.filter(span => {
|
|
35
|
-
const target = span?.attributes?.http?.target;
|
|
35
|
+
const target = span?.attributes?.http?.target;
|
|
36
|
+
// Exclude spans where target includes 'telemetry' but NOT 'telemetry/utils/generate-log' or 'telemetry/utils/generate-wait'
|
|
36
37
|
if (target && target.includes(this._baseUrl)) {
|
|
37
|
-
return target.includes(
|
|
38
|
+
return (target.includes("generate"));
|
|
38
39
|
}
|
|
39
40
|
return true;
|
|
40
41
|
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (err) {
|
|
44
|
-
logger.error(err);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
42
|
+
cleanSpans.forEach(span => {
|
|
43
|
+
pluginService.broadcastTrace(span);
|
|
47
44
|
});
|
|
48
|
-
|
|
45
|
+
//
|
|
46
|
+
if (this.isEnabled()) {
|
|
47
|
+
// Insert spans into the in-memory database
|
|
48
|
+
this._spans.insert(cleanSpans, (err, _newDoc) => {
|
|
49
|
+
if (err) {
|
|
50
|
+
logger.error(err);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return resultCallback({ code: ExportResultCode.SUCCESS });
|
|
49
56
|
}
|
|
50
57
|
catch (error) {
|
|
51
58
|
logger.error('Error exporting spans\n' + error.message + '\n' + error.stack);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
|
|
2
2
|
import logger from '../utils/logger.js';
|
|
3
3
|
import { registerInstrumentations } from '@opentelemetry/instrumentation';
|
|
4
|
-
import { dynamicMultiLogProcessor, dynamicMultiSpanProcessor, oasTelemetryResource } from './telemetryRegistry.js';
|
|
4
|
+
import { dynamicMultiLogProcessor, dynamicMultiSpanProcessor, oasTelemetryResource, originalConsoleMethods } from './telemetryRegistry.js';
|
|
5
5
|
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
6
6
|
import { SeverityNumber } from '@opentelemetry/api-logs';
|
|
7
7
|
import { LoggerProvider } from '@opentelemetry/sdk-logs';
|
|
8
8
|
import { bootEnvVariables } from '../config/bootConfig.js';
|
|
9
|
+
import util from 'util';
|
|
9
10
|
// THIS INSTRUMENTATIONS NEED TO BE LOADED BEFORE ANYTHING ELSE
|
|
10
11
|
// They use monkey-patching to instrument the HTTP server and client.
|
|
11
12
|
// THIS FILE MUST BE CALLED BEFORE ANYTHING ELSE
|
|
@@ -41,22 +42,15 @@ function initializeLogs() {
|
|
|
41
42
|
});
|
|
42
43
|
// Get a logger instance
|
|
43
44
|
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
45
|
Object.keys(originalConsoleMethods).forEach((method) => {
|
|
53
46
|
// @ts-expect-error yes
|
|
54
47
|
console[method] = (...args) => {
|
|
48
|
+
const severity = getSeverityForMethod(method);
|
|
55
49
|
loggerInstance.emit({
|
|
56
|
-
severityNumber:
|
|
57
|
-
severityText:
|
|
58
|
-
body:
|
|
59
|
-
attributes: { 'source
|
|
50
|
+
severityNumber: severity.number,
|
|
51
|
+
severityText: severity.text,
|
|
52
|
+
body: util.format(...args),
|
|
53
|
+
attributes: { 'source': `console.${method}`, "library": "oas-telemetry" },
|
|
60
54
|
});
|
|
61
55
|
// @ts-expect-error yes
|
|
62
56
|
originalConsoleMethods[method](...args);
|
|
@@ -65,7 +59,21 @@ function initializeLogs() {
|
|
|
65
59
|
}
|
|
66
60
|
function initializeMetrics() {
|
|
67
61
|
logger.info('📈 Initializing MeterProvider');
|
|
68
|
-
// WARN: This is a custom provider that allows adding readers dynamically at runtime.
|
|
69
62
|
// WARN: Default PeriodicExportingMetricReader is added post initialization (see telemetryConfigurator.ts)
|
|
70
63
|
// The in memory exporter is added by default to that reader. More readers are allowed to be added dynamically
|
|
71
64
|
}
|
|
65
|
+
function getSeverityForMethod(method) {
|
|
66
|
+
switch (method) {
|
|
67
|
+
case "log":
|
|
68
|
+
case "info":
|
|
69
|
+
return { number: SeverityNumber.INFO, text: "INFO" };
|
|
70
|
+
case "debug":
|
|
71
|
+
return { number: SeverityNumber.DEBUG, text: "DEBUG" };
|
|
72
|
+
case "warn":
|
|
73
|
+
return { number: SeverityNumber.WARN, text: "WARN" };
|
|
74
|
+
case "error":
|
|
75
|
+
return { number: SeverityNumber.ERROR, text: "ERROR" };
|
|
76
|
+
default:
|
|
77
|
+
return { number: SeverityNumber.INFO, text: "INFO" };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import { BatchSpanProcessor, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
|
|
2
|
-
import { dynamicMultiLogProcessor, dynamicMultiSpanProcessor, inMemoryDbLogExporter, inMemoryDbMetricExporter, inMemoryDbSpanExporter, multiLogExporter, multiSpanExporter, oasTelemetryResource
|
|
2
|
+
import { dynamicMultiLogProcessor, dynamicMultiSpanProcessor, inMemoryDbLogExporter, inMemoryDbMetricExporter, inMemoryDbSpanExporter, multiLogExporter, multiSpanExporter, oasTelemetryResource } from './telemetryRegistry.js';
|
|
3
3
|
import logger from '../utils/logger.js';
|
|
4
4
|
import { BatchLogRecordProcessor, SimpleLogRecordProcessor } from '@opentelemetry/sdk-logs';
|
|
5
5
|
import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
|
|
6
6
|
import { HostMetrics } from '@opentelemetry/host-metrics';
|
|
7
7
|
import { bootEnvVariables } from '../config/bootConfig.js';
|
|
8
|
+
import { pluginService } from '../tlm-plugin/pluginService.js';
|
|
8
9
|
export const configureTelemetry = (oasTlmConfig) => {
|
|
10
|
+
configurePlugins(oasTlmConfig);
|
|
9
11
|
configureTraces(oasTlmConfig);
|
|
10
12
|
configureMetrics(oasTlmConfig);
|
|
11
13
|
configureLogs(oasTlmConfig);
|
|
12
14
|
logger.info("✅ Telemetry configured successfully. All exporters are ready");
|
|
13
15
|
};
|
|
16
|
+
function configurePlugins(oasTlmConfig) {
|
|
17
|
+
pluginService.enabled = oasTlmConfig.plugins.enabled;
|
|
18
|
+
}
|
|
14
19
|
function configureTraces(oasTlmConfig) {
|
|
15
20
|
// TRACES CONFIGURATION
|
|
16
21
|
// [OT]Provider -> [OT]SpanProcessor(multiSpan) -> n Processors(eg mainProcessor, extra) -> 1 SpanExporter
|
|
17
22
|
inMemoryDbSpanExporter.baseUrl = oasTlmConfig.general.baseUrl; // TODO this will be done with filters
|
|
18
23
|
inMemoryDbSpanExporter.retentionTimeInSeconds = oasTlmConfig.traces.memoryExporter.retentionTimeSeconds;
|
|
19
24
|
inMemoryDbSpanExporter.setEnabledValue(oasTlmConfig.traces.memoryExporter.enabled);
|
|
20
|
-
pluginSpanExporter.setEnabledValue(oasTlmConfig.plugins.enabled);
|
|
21
25
|
const mainExporter = multiSpanExporter;
|
|
22
26
|
let mainProcessor = new BatchSpanProcessor(mainExporter);
|
|
23
27
|
if (bootEnvVariables.OASTLM_BOOT_ENV !== 'production') {
|
|
@@ -25,7 +29,6 @@ function configureTraces(oasTlmConfig) {
|
|
|
25
29
|
mainProcessor = new SimpleSpanProcessor(mainExporter);
|
|
26
30
|
}
|
|
27
31
|
mainExporter.addExporters(inMemoryDbSpanExporter); // Main exporter have at least the in-memory exporter used by the traces controller
|
|
28
|
-
mainExporter.addExporters(pluginSpanExporter);
|
|
29
32
|
mainExporter.addExporters(oasTlmConfig.traces.extraExporters);
|
|
30
33
|
dynamicMultiSpanProcessor.addProcessors(mainProcessor);
|
|
31
34
|
dynamicMultiSpanProcessor.addProcessors(oasTlmConfig.traces.extraProcessors);
|
|
@@ -43,7 +46,6 @@ function configureLogs(oasTlmConfig) {
|
|
|
43
46
|
}
|
|
44
47
|
mainExporter.addExporters(inMemoryDbLogExporter); // Main exporter have at least the in-memory exporter used by the logs controller
|
|
45
48
|
mainExporter.addExporters(oasTlmConfig.logs.extraExporters);
|
|
46
|
-
mainExporter.addExporters(pluginLogExporter); // Allow logs to be sent to plugins too
|
|
47
49
|
dynamicMultiLogProcessor.addProcessors(mainProcessor);
|
|
48
50
|
dynamicMultiLogProcessor.addProcessors(oasTlmConfig.logs.extraProcessors);
|
|
49
51
|
}
|
|
@@ -57,14 +59,9 @@ function configureMetrics(oasTlmConfig) {
|
|
|
57
59
|
exportIntervalMillis: oasTlmConfig.metrics.mainMetricReaderOptions.exportIntervalMillis,
|
|
58
60
|
metricProducers: oasTlmConfig.metrics.mainMetricReaderOptions.metricProducers
|
|
59
61
|
});
|
|
60
|
-
const pluginReader = new PeriodicExportingMetricReader({
|
|
61
|
-
exporter: pluginMetricExporter,
|
|
62
|
-
exportIntervalMillis: oasTlmConfig.metrics.mainMetricReaderOptions.exportIntervalMillis,
|
|
63
|
-
metricProducers: oasTlmConfig.metrics.mainMetricReaderOptions.metricProducers
|
|
64
|
-
});
|
|
65
62
|
const meterProvider = new MeterProvider({
|
|
66
63
|
resource: oasTelemetryResource,
|
|
67
|
-
readers: [mainReader,
|
|
64
|
+
readers: [mainReader, ...oasTlmConfig.metrics.extraReaders],
|
|
68
65
|
views: oasTlmConfig.metrics.extraViews || []
|
|
69
66
|
});
|
|
70
67
|
// TODO maybe hostMetrics are too much, consider using only a subset of them.
|
|
@@ -6,12 +6,10 @@ import { DynamicMultiSpanProcessor } from "./custom-implementations/processors/d
|
|
|
6
6
|
import { DynamicMultiLogRecordProcessor } from "./custom-implementations/processors/dynamicMultiLogProcessor.js";
|
|
7
7
|
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
8
8
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
9
|
-
import {
|
|
10
|
-
import { PluginLogExporter } from "./custom-implementations/exporters/PluginLogExporter.js";
|
|
11
|
-
import { PluginMetricExporter } from "./custom-implementations/exporters/PluginMetricExporter.js";
|
|
9
|
+
import { bootEnvVariables } from "../config/bootConfig.js";
|
|
12
10
|
// GLOBAL REGISTRY of telemetry components, used by SDKs and controllers.
|
|
13
11
|
export const oasTelemetryResource = resourceFromAttributes({
|
|
14
|
-
[ATTR_SERVICE_NAME]:
|
|
12
|
+
[ATTR_SERVICE_NAME]: bootEnvVariables.OASTLM_BOOT_SERVICE_NAME
|
|
15
13
|
});
|
|
16
14
|
// TRACES -------------------------------------------------------------------------------------
|
|
17
15
|
// This is the main exporter for oas-telemetry spans. (Used by the traces controller)
|
|
@@ -20,15 +18,20 @@ export const inMemoryDbSpanExporter = new InMemoryDbSpanExporter();
|
|
|
20
18
|
export const multiSpanExporter = new EnablerMultiSpanExporter();
|
|
21
19
|
// This allows the addition of more processors at runtime
|
|
22
20
|
export const dynamicMultiSpanProcessor = new DynamicMultiSpanProcessor();
|
|
23
|
-
export const pluginSpanExporter = new PluginSpanExporter(); // This exporter sends spans to the plugin module.
|
|
24
21
|
// LOGS ----------------------------------------------------------------------------------------
|
|
25
22
|
export const inMemoryDbLogExporter = new InMemoryDbLogExporter();
|
|
26
23
|
export const multiLogExporter = new EnablerMultiLogExporter();
|
|
27
24
|
export const dynamicMultiLogProcessor = new DynamicMultiLogRecordProcessor();
|
|
28
|
-
|
|
25
|
+
// Override console methods to emit logs via OpenTelemetry, like an instrumentation
|
|
26
|
+
export const originalConsoleMethods = {
|
|
27
|
+
log: console.log,
|
|
28
|
+
warn: console.warn,
|
|
29
|
+
error: console.error,
|
|
30
|
+
info: console.info,
|
|
31
|
+
debug: console.debug,
|
|
32
|
+
};
|
|
29
33
|
// METRICS -------------------------------------------------------------------------------------
|
|
30
34
|
// Metrics follow a different pattern in OpenTelemetry
|
|
31
35
|
export const inMemoryDbMetricExporter = new InMemoryDbMetricExporter();
|
|
32
|
-
export const pluginMetricExporter = new PluginMetricExporter(); // This exporter sends metrics to the plugin module.
|
|
33
36
|
// Readers and their exporters cannot be grouped together in a MultiReader or similar construct
|
|
34
37
|
// due to differences in aggregation temporality and aggregation selection.
|
package/dist/esm/tlm-ai/agent.js
CHANGED
|
@@ -1,86 +1,45 @@
|
|
|
1
|
-
import OpenAI from 'openai';
|
|
2
1
|
import { tools, availableTools } from './tools.js';
|
|
3
2
|
import logger from '../utils/logger.js';
|
|
4
|
-
export function
|
|
5
|
-
let
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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) {
|
|
29
|
-
messages.push({
|
|
30
|
-
role: "system",
|
|
31
|
-
content: prompt,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
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." };
|
|
39
|
-
}
|
|
40
|
-
messages.push({
|
|
41
|
-
role: "user",
|
|
42
|
-
content: userInput,
|
|
3
|
+
export async function agent(openai, messages, model = "gpt-3.5-turbo", extraPrompts = []) {
|
|
4
|
+
for (let i = 0; i < 5; i++) {
|
|
5
|
+
const modelResponse = await openai.chat.completions.create({
|
|
6
|
+
model,
|
|
7
|
+
messages,
|
|
8
|
+
tools,
|
|
43
9
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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,
|
|
10
|
+
const { finish_reason, message } = modelResponse.choices[0];
|
|
11
|
+
if (finish_reason === "tool_calls" && message.tool_calls) {
|
|
12
|
+
logger.debug("Tool calls detected:", message.tool_calls);
|
|
13
|
+
const results = [];
|
|
14
|
+
for (const toolCall of message.tool_calls) {
|
|
15
|
+
const functionName = toolCall.function.name;
|
|
16
|
+
const functionToCall = availableTools[functionName];
|
|
17
|
+
const functionArgs = JSON.parse(toolCall.function.arguments);
|
|
18
|
+
const functionArgsArr = Object.values(functionArgs);
|
|
19
|
+
// @ts-expect-error yes
|
|
20
|
+
// eslint-disable-next-line prefer-spread
|
|
21
|
+
const functionResponse = await functionToCall.apply(null, functionArgsArr);
|
|
22
|
+
results.push({
|
|
23
|
+
name: functionName,
|
|
24
|
+
response: functionResponse,
|
|
72
25
|
});
|
|
73
26
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
27
|
+
const resultMessage = results.map(({ name, response }, idx) => {
|
|
28
|
+
const toolCall = message.tool_calls?.[idx];
|
|
29
|
+
const params = toolCall ? JSON.parse(toolCall.function.arguments) : {};
|
|
30
|
+
return `Tool "${name}" called with parameters:\n${JSON.stringify(params, null, 2)}\nResult:\n${JSON.stringify(response, null, 2)}`;
|
|
31
|
+
}).join("\n\n");
|
|
32
|
+
messages.push({
|
|
33
|
+
role: "function",
|
|
34
|
+
name: "multiple_tool_calls",
|
|
35
|
+
content: resultMessage,
|
|
36
|
+
timestamp: new Date().toISOString()
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
else if (finish_reason === "stop") {
|
|
40
|
+
messages.push({ ...message, timestamp: new Date().toISOString() });
|
|
41
|
+
return;
|
|
78
42
|
}
|
|
79
|
-
return { content: "Se alcanzó el número máximo de iteraciones sin una respuesta adecuada. Intenta con una consulta más específica." };
|
|
80
43
|
}
|
|
81
|
-
|
|
82
|
-
const response = await agent(question);
|
|
83
|
-
logger.debug("Response from agent:", response);
|
|
84
|
-
return response.content;
|
|
85
|
-
};
|
|
44
|
+
messages.push({ role: "assistant", content: "Maximum iterations reached without a suitable response. Try a more specific query.", timestamp: new Date().toISOString() });
|
|
86
45
|
}
|
|
@@ -1,46 +1,63 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import logger from '../utils/logger.js';
|
|
4
|
-
export const answerQuestion = (oasTlmConfig) => async (req, res) => {
|
|
1
|
+
import { getAiService } from './aiService.js';
|
|
2
|
+
export async function createConversation(req, res) {
|
|
5
3
|
try {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
4
|
+
const conversation = getAiService().createConversation();
|
|
5
|
+
res.status(201).json(conversation);
|
|
6
|
+
}
|
|
7
|
+
catch (err) {
|
|
8
|
+
res.status(500).json({ error: err.message });
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export async function listConversations(req, res) {
|
|
12
|
+
try {
|
|
13
|
+
// Only return id and name for each conversation
|
|
14
|
+
const conversations = getAiService().listConversationsMinimal();
|
|
15
|
+
res.json(conversations);
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
res.status(500).json({ error: err.message });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function getConversationHistory(req, res) {
|
|
22
|
+
try {
|
|
23
|
+
const { conversationId } = req.params;
|
|
24
|
+
const conversation = getAiService().getConversation(conversationId);
|
|
25
|
+
if (!conversation) {
|
|
26
|
+
res.status(404).json({ error: 'Not found' });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
res.json(conversation);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
res.status(500).json({ error: err.message });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function deleteConversation(req, res) {
|
|
19
36
|
try {
|
|
20
|
-
const {
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
const { conversationId } = req.params;
|
|
38
|
+
const deleted = getAiService().deleteConversation(conversationId);
|
|
39
|
+
if (!deleted) {
|
|
40
|
+
res.status(404).json({ error: 'Not found' });
|
|
23
41
|
return;
|
|
24
42
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
catch (error) {
|
|
33
|
-
logger.error(error);
|
|
34
|
-
res.status(500).json({ error: 'Internal error' });
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
export const getKnownMicroservicesHandler = () => (req, res) => {
|
|
43
|
+
res.status(204).send();
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
res.status(500).json({ error: err.message });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function sendMessage(req, res) {
|
|
38
50
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
51
|
+
const { conversationId } = req.params;
|
|
52
|
+
const { content } = req.body;
|
|
53
|
+
if (!content) {
|
|
54
|
+
res.status(400).json({ error: 'Missing content' });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const messages = await getAiService().sendMessage(conversationId, content);
|
|
58
|
+
res.json(messages);
|
|
41
59
|
}
|
|
42
|
-
catch (
|
|
43
|
-
|
|
44
|
-
res.status(500).json({ error: 'Internal error' });
|
|
60
|
+
catch (err) {
|
|
61
|
+
res.status(500).json({ error: err.message });
|
|
45
62
|
}
|
|
46
|
-
}
|
|
63
|
+
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
-
import {
|
|
2
|
+
import { createConversation, deleteConversation, getConversationHistory, listConversations, sendMessage } from './aiController.js';
|
|
3
|
+
import { configureAiService } from './aiService.js';
|
|
3
4
|
export const getAIRoutes = (oasTlmConfig) => {
|
|
4
5
|
const router = Router();
|
|
5
|
-
|
|
6
|
-
router.
|
|
7
|
-
|
|
6
|
+
configureAiService(oasTlmConfig); //oasTlmConfig.ai.openAIKey
|
|
7
|
+
router.get('/chat/health', (req, res) => {
|
|
8
|
+
res.status(200).send('AI service is healthy');
|
|
9
|
+
});
|
|
10
|
+
router.get('/chat', listConversations);
|
|
11
|
+
router.post('/chat', createConversation);
|
|
12
|
+
router.get('/chat/:conversationId', getConversationHistory);
|
|
13
|
+
router.post('/chat/:conversationId/message', sendMessage);
|
|
14
|
+
router.delete('/chat/:conversationId', deleteConversation);
|
|
8
15
|
return router;
|
|
9
16
|
};
|