From 6b86c348fe5cddfdee8dd8fcae2aef307ecc879a Mon Sep 17 00:00:00 2001 From: "Peter.Morton" Date: Fri, 9 Jan 2026 23:11:07 -0600 Subject: [PATCH] Better heirarchy and option to do conversation tracing vs transaction. --- open-telemetry/README.md | 29 +-- .../GlobalVariable/opentelemetry.js | 241 +++++++++++------- 2 files changed, 158 insertions(+), 112 deletions(-) diff --git a/open-telemetry/README.md b/open-telemetry/README.md index a7209b9..11ec709 100644 --- a/open-telemetry/README.md +++ b/open-telemetry/README.md @@ -8,45 +8,44 @@ Use the _code block_ widget to start and end spans. Spans can be nested to form ```javascript (async () => { - await latencySpan().endSpan(recognizedObject.conversationId); - - const span = await opentelemetry().startSpan("Global Flow"); - console.log(span); + opentelemetry.startSpan("Global Flow") })() .catch((error) => { - console.log(error.message); - recognizedObject.answers.push(""); + console.error(error.message) + recognizedObject.answers.push('') recognizedObject.errorInfo = { ...recognizedObject.errorInfo, label: { data: error.toJSON ? error.toJSON() : {}, message: error.message, }, - }; + } }) .finally(() => { - next(); - }); + next() + }) + ``` ### End Span ```javascript (async () => { - await opentelemetry().endSpan(); + opentelemetry.endSpan("Global Flow") })() .catch((error) => { - console.log(error.message); - recognizedObject.answers.push(""); + console.error(error.message) + recognizedObject.answers.push('') recognizedObject.errorInfo = { ...recognizedObject.errorInfo, label: { data: error.toJSON ? error.toJSON() : {}, message: error.message, }, - }; + } }) .finally(() => { - next(); - }); + next() + }) + ``` \ No newline at end of file diff --git a/open-telemetry/_studio_dependencies/GlobalVariable/opentelemetry.js b/open-telemetry/_studio_dependencies/GlobalVariable/opentelemetry.js index 09c0feb..a0d0992 100644 --- a/open-telemetry/_studio_dependencies/GlobalVariable/opentelemetry.js +++ b/open-telemetry/_studio_dependencies/GlobalVariable/opentelemetry.js @@ -1,105 +1,152 @@ -const traces = axios.create({ - baseURL: opentelemetry_settings.baseUrl, - timeout: 16000, -}) +const { + recognizedObject: r = {}, + traces = axios.create({ + baseURL: opentelemetry_settings.baseUrl, + timeout: 16000, + }), +} = this; + +function generateIdHex(numBytes = 8) { + const bytes = crypto.getRandomValues(new Uint8Array(numBytes)); + return Array.from(bytes) + .map(b => b.toString(16).padStart(2, '0')) + .join(''); +} + return { - async startSpan(name) { - try { - if (!conversationData.spans) { - conversationData.spans = [] - } - const spanName = name ? name : 'default' + async startSpan(name) { + if (name == null) { + throw new Error("Span name is required"); + } - const span = { - resourceSpans: [ - { - resource: { - attributes: [ - { - key: 'service.name', - value: { - stringValue: 'ivastudio.verint.live', - }, - }, - ], - }, - scopeSpans: [ - { - scope: { - name: recognizedObject.conversationId, - version: '1.0.0', - attributes: [ + // Initialize telemetry object if it doesn't exist + if ("telemetry" in r == false) { + r.telemetry = []; + if (opentelemetry_settings.trace == "conversation") { + r.telemetry.traceId = r.conversationId.replace(/-/gi, ""); + } else { + r.telemetry.traceId = generateIdHex(16); + } + } + + try { + const span = { + name: name, + resourceSpans: [ { - key: 'my.scope.attribute', - value: { - stringValue: 'some scope attribute', - }, + resource: { + attributes: [ + { + key: "service.name", + value: { + stringValue: "ivastudio.verint.live", + }, + }, + ], + }, + scopeSpans: [ + { + scope: { + name: r.conversationId, + version: "1.0.0", + attributes: [ + { + key: "my.scope.attribute", + value: { + stringValue: "some scope attribute", + }, + }, + ], + }, + spans: [ + { + traceId: r.telemetry.traceId, + spanId: generateIdHex(), + name: name, + startTimeUnixNano: "" + Date.now() * 1000000, + kind: 2, + attributes: [ + { + key: "conversationId", + value: { + stringValue: r.conversationId, + }, + }, + { + key: "workspaceId", + value: { + stringValue: r.workspaceId, + }, + }, + { + key: "input", + value: { + stringValue: r.input, + }, + }, + ], + }, + ], + }, + ], }, - ], - }, - spans: [ - { - // traceId: recognizedObject.req.headers['x-b3-traceid'], - traceId: recognizedObject.conversationId.replace(/-/gi, ''), - spanId: recognizedObject.req.headers['x-b3-spanid'], - // parentSpanId: recognizedObject.req.headers['x-b3-parentspanid'], - name: spanName, - startTimeUnixNano: '' + Date.now() * 1000000, - kind: 2, - attributes: [ - { - key: 'conversationId', - value: { - stringValue: recognizedObject.conversationId, - }, - }, - { - key: 'workspaceId', - value: { - stringValue: recognizedObject.workspaceId, - }, - }, - { - key: 'input', - value: { - stringValue: recognizedObject.input, - }, - }, - ], - }, ], - }, - ], - }, - ], - } - conversationData.spans.push(span) - return span - } catch (e) { - throw e - } - }, - async endSpan() { - try { - const data = conversationData.spans.pop() + }; + r.telemetry.unshift(span); + return span; + } catch (e) { + throw e; + } + }, + async endSpan(name) { + if (name == null) { + throw new Error("Span name is required"); + } - data.resourceSpans[0].scopeSpans[0].spans[0].endTimeUnixNano = '' + Date.now() * 1000000 + if ("telemetry" in r == false) { + // nothing to end + return; + } - data.resourceSpans[0].scopeSpans[0].spans[0].attributes.push({ - key: 'answers', - value: { - stringValue: JSON.stringify(recognizedObject.answers), - }, - }) + try { + const spanIndex = r.telemetry.findIndex((span) => span.name === name); - const response = await traces.post(`/v1/traces`, data, { - headers: { - 'Content-Type': 'application/json', - }, - }) - return response.data - } catch (e) { - throw e - } - }, -} \ No newline at end of file + if (spanIndex == -1) { + // nothing to end + return; + } + + const span = r.telemetry.splice(spanIndex, 1)[0]; + + if (r.telemetry.length >= 1) { + // Assume span now at index is the parent + const parentSpan = r.telemetry[spanIndex]; + if (parentSpan) { + span.resourceSpans[0].scopeSpans[0].spans[0].parentSpanId = + parentSpan.resourceSpans[0].scopeSpans[0].spans[0].spanId; + } else { + console.debug(name + " has no parentSpan"); + } + } + + span.resourceSpans[0].scopeSpans[0].spans[0].endTimeUnixNano = + "" + Date.now() * 1000000; + + span.resourceSpans[0].scopeSpans[0].spans[0].attributes.push({ + key: "answers", + value: { + stringValue: JSON.stringify(r.answers), + }, + }); + + const response = await traces.post(`/v1/traces`, span, { + headers: { + "Content-Type": "application/json", + }, + }); + return response.data; + } catch (e) { + throw e; + } + }, +};