UNPKG

@mastra/core

Version:

Mastra is the Typescript framework for building AI agents and assistants. It’s used by some of the largest companies in the world to build internal AI automation tooling and customer-facing agents.

596 lines (593 loc) • 20.6 kB
import { TABLE_TRACES } from './chunk-R5GESRGB.js'; import { MastraError } from './chunk-MCOVMKIS.js'; import { trace, propagation, context, SpanStatusCode, SpanKind } from '@opentelemetry/api'; import { ExportResultCode } from '@opentelemetry/core'; import { JsonTraceSerializer } from '@opentelemetry/otlp-transformer'; function hasActiveTelemetry(tracerName = "default-tracer") { try { return !!trace.getTracer(tracerName); } catch { return false; } } function getBaggageValues(ctx) { const currentBaggage = propagation.getBaggage(ctx); const requestId = currentBaggage?.getEntry("http.request_id")?.value; const componentName = currentBaggage?.getEntry("componentName")?.value; const runId = currentBaggage?.getEntry("runId")?.value; const threadId = currentBaggage?.getEntry("threadId")?.value; const resourceId = currentBaggage?.getEntry("resourceId")?.value; return { requestId, componentName, runId, threadId, resourceId }; } // src/telemetry/telemetry.decorators.ts function isStreamingResult(result, methodName) { if (methodName === "stream" || methodName === "streamVNext") { return true; } if (result && typeof result === "object" && result !== null) { const obj = result; return "textStream" in obj || "objectStream" in obj || "usagePromise" in obj || "finishReasonPromise" in obj; } return false; } function enhanceStreamingArgumentsWithTelemetry(args, span, spanName, methodName) { if (methodName === "stream" || methodName === "streamVNext") { const enhancedArgs = [...args]; const streamOptions = enhancedArgs.length > 1 && enhancedArgs[1] || {}; const enhancedStreamOptions = { ...streamOptions }; const originalOnFinish = enhancedStreamOptions.onFinish; enhancedStreamOptions.onFinish = async (finishData) => { try { const telemetryData = { text: finishData.text, usage: finishData.usage, finishReason: finishData.finishReason, toolCalls: finishData.toolCalls, toolResults: finishData.toolResults, warnings: finishData.warnings, ...finishData.object !== void 0 && { object: finishData.object } }; span.setAttribute(`${spanName}.result`, JSON.stringify(telemetryData)); span.setStatus({ code: SpanStatusCode.OK }); span.end(); } catch (error) { console.warn("Telemetry capture failed:", error); span.setAttribute(`${spanName}.result`, "[Telemetry Capture Error]"); span.setStatus({ code: SpanStatusCode.ERROR }); span.end(); } if (originalOnFinish) { return await originalOnFinish(finishData); } }; enhancedStreamOptions.onFinish.__hasOriginalOnFinish = !!originalOnFinish; enhancedArgs[1] = enhancedStreamOptions; span.__mastraStreamingSpan = true; return enhancedArgs; } return args; } function withSpan(options) { return function(_target, propertyKey, descriptor) { if (!descriptor || typeof descriptor === "number") return; const originalMethod = descriptor.value; const methodName = String(propertyKey); descriptor.value = function(...args) { if (options?.skipIfNoTelemetry && !hasActiveTelemetry(options?.tracerName)) { return originalMethod.apply(this, args); } const tracer = trace.getTracer(options?.tracerName ?? "default-tracer"); let spanName; let spanKind; if (typeof options === "string") { spanName = options; } else if (options) { spanName = options.spanName || methodName; spanKind = options.spanKind; } else { spanName = methodName; } const span = tracer.startSpan(spanName, { kind: spanKind }); let ctx = trace.setSpan(context.active(), span); args.forEach((arg, index) => { try { span.setAttribute(`${spanName}.argument.${index}`, JSON.stringify(arg)); } catch { span.setAttribute(`${spanName}.argument.${index}`, "[Not Serializable]"); } }); const { requestId, componentName, runId, threadId, resourceId } = getBaggageValues(ctx); if (requestId) { span.setAttribute("http.request_id", requestId); } if (threadId) { span.setAttribute("threadId", threadId); } if (resourceId) { span.setAttribute("resourceId", resourceId); } if (componentName) { span.setAttribute("componentName", componentName); span.setAttribute("runId", runId); } else if (this && typeof this === "object" && "name" in this) { const contextObj = this; span.setAttribute("componentName", contextObj.name); if (contextObj.runId) { span.setAttribute("runId", contextObj.runId); } ctx = propagation.setBaggage( ctx, propagation.createBaggage({ // @ts-ignore componentName: { value: this.name }, // @ts-ignore runId: { value: this.runId }, // @ts-ignore "http.request_id": { value: requestId }, // @ts-ignore threadId: { value: threadId }, // @ts-ignore resourceId: { value: resourceId } }) ); } let result; try { const enhancedArgs = isStreamingResult(result, methodName) ? enhanceStreamingArgumentsWithTelemetry(args, span, spanName, methodName) : args; result = context.with(ctx, () => originalMethod.apply(this, enhancedArgs)); if (result instanceof Promise) { return result.then((resolvedValue) => { if (isStreamingResult(resolvedValue, methodName)) { return resolvedValue; } else { try { span.setAttribute(`${spanName}.result`, JSON.stringify(resolvedValue)); } catch { span.setAttribute(`${spanName}.result`, "[Not Serializable]"); } return resolvedValue; } }).finally(() => { if (!span.__mastraStreamingSpan) { span.end(); } }); } if (!isStreamingResult(result, methodName)) { try { span.setAttribute(`${spanName}.result`, JSON.stringify(result)); } catch { span.setAttribute(`${spanName}.result`, "[Not Serializable]"); } } return result; } catch (error) { span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : "Unknown error" }); if (error instanceof Error) { span.recordException(error); } throw error; } finally { if (!(result instanceof Promise) && !isStreamingResult(result, methodName)) { span.end(); } } }; return descriptor; }; } function InstrumentClass(options) { return function(target) { const methods = Object.getOwnPropertyNames(target.prototype); methods.forEach((method) => { if (options?.excludeMethods?.includes(method) || method === "constructor") return; if (options?.methodFilter && !options.methodFilter(method)) return; const descriptor = Object.getOwnPropertyDescriptor(target.prototype, method); if (descriptor && typeof descriptor.value === "function") { Object.defineProperty( target.prototype, method, withSpan({ spanName: options?.prefix ? `${options.prefix}.${method}` : method, skipIfNoTelemetry: true, spanKind: options?.spanKind || SpanKind.INTERNAL, tracerName: options?.tracerName })(target, method, descriptor) ); } }); return target; }; } var OTLPTraceExporter = class { storage; queue = []; serializer; logger; activeFlush = void 0; constructor({ logger, storage }) { this.storage = storage; this.serializer = JsonTraceSerializer; this.logger = logger; } export(internalRepresentation, resultCallback) { const serializedRequest = this.serializer.serializeRequest(internalRepresentation); const payload = JSON.parse(Buffer.from(serializedRequest.buffer, "utf8")); const items = payload?.resourceSpans?.[0]?.scopeSpans; this.logger.debug(`Exporting telemetry: ${items.length} scope spans to be processed [trace batch]`); this.queue.push({ data: items, resultCallback }); if (!this.activeFlush) { this.activeFlush = this.flush(); } } shutdown() { return this.forceFlush(); } flush() { const now = /* @__PURE__ */ new Date(); const items = this.queue.shift(); if (!items) return Promise.resolve(); const allSpans = items.data.reduce((acc, scopedSpans) => { const { scope, spans } = scopedSpans; for (const span of spans) { const { spanId, parentSpanId, traceId, name, kind, attributes, status, events, links, startTimeUnixNano, endTimeUnixNano, ...rest } = span; const startTime = Number(BigInt(startTimeUnixNano) / 1000n); const endTime = Number(BigInt(endTimeUnixNano) / 1000n); acc.push({ id: spanId, parentSpanId, traceId, name, scope: scope.name, kind, status: JSON.stringify(status), events: JSON.stringify(events), links: JSON.stringify(links), attributes: JSON.stringify( attributes.reduce((acc2, attr) => { const valueKey = Object.keys(attr.value)[0]; if (valueKey) { acc2[attr.key] = attr.value[valueKey]; } return acc2; }, {}) ), startTime, endTime, other: JSON.stringify(rest), createdAt: now }); } return acc; }, []); return this.storage.batchInsert({ tableName: TABLE_TRACES, records: allSpans }).then(() => { items.resultCallback({ code: ExportResultCode.SUCCESS }); }).catch((e) => { const mastraError = new MastraError( { id: "OTLP_TRACE_EXPORT_FAILURE", text: "Failed to export telemetry spans", domain: "MASTRA_TELEMETRY" /* MASTRA_TELEMETRY */, category: "SYSTEM" /* SYSTEM */, details: { attemptedSpanCount: allSpans.length, targetTable: TABLE_TRACES, firstSpanName: allSpans.length > 0 ? allSpans[0].name : "", firstSpanKind: allSpans.length > 0 ? allSpans[0].kind : "", firstSpanScope: allSpans.length > 0 ? allSpans[0].scope : "" } }, e ); this.logger.trackException(mastraError); this.logger.error("span err:" + mastraError.toString()); items.resultCallback({ code: ExportResultCode.FAILED, error: e }); }).finally(() => { this.activeFlush = void 0; }); } async forceFlush() { if (!this.queue.length) { return; } await this.activeFlush; while (this.queue.length) { await this.flush(); } } __setLogger(logger) { this.logger = logger; } }; var Telemetry = class _Telemetry { tracer = trace.getTracer("default"); name = "default-service"; constructor(config) { this.name = config.serviceName ?? "default-service"; this.tracer = trace.getTracer(this.name); } /** * @deprecated This method does not do anything */ async shutdown() { } /** * Initialize telemetry with the given configuration * @param config - Optional telemetry configuration object * @returns Telemetry instance that can be used for tracing */ static init(config = {}) { try { if (!globalThis.__TELEMETRY__) { globalThis.__TELEMETRY__ = new _Telemetry(config); } return globalThis.__TELEMETRY__; } catch (error) { const wrappedError = new MastraError( { id: "TELEMETRY_INIT_FAILED", text: "Failed to initialize telemetry", domain: "MASTRA_TELEMETRY" /* MASTRA_TELEMETRY */, category: "SYSTEM" /* SYSTEM */ }, error ); throw wrappedError; } } static getActiveSpan() { const span = trace.getActiveSpan(); return span; } /** * Get the global telemetry instance * @throws {Error} If telemetry has not been initialized * @returns {Telemetry} The global telemetry instance */ static get() { if (!globalThis.__TELEMETRY__) { throw new MastraError({ id: "TELEMETRY_GETTER_FAILED_GLOBAL_TELEMETRY_NOT_INITIALIZED", text: "Telemetry not initialized", domain: "MASTRA_TELEMETRY" /* MASTRA_TELEMETRY */, category: "USER" /* USER */ }); } return globalThis.__TELEMETRY__; } /** * Wraps a class instance with telemetry tracing * @param instance The class instance to wrap * @param options Optional configuration for tracing * @returns Wrapped instance with all methods traced */ traceClass(instance, options = {}) { const { skipIfNoTelemetry = true } = options; if (skipIfNoTelemetry && !hasActiveTelemetry()) { return instance; } const { spanNamePrefix = instance.constructor.name.toLowerCase(), attributes = {}, excludeMethods = [] } = options; return new Proxy(instance, { get: (target, prop) => { const value = target[prop]; if (typeof value === "function" && prop !== "constructor" && !prop.toString().startsWith("_") && !excludeMethods.includes(prop.toString())) { return this.traceMethod(value.bind(target), { spanName: `${spanNamePrefix}.${prop.toString()}`, attributes: { ...attributes, [`${spanNamePrefix}.name`]: target.constructor.name, [`${spanNamePrefix}.method.name`]: prop.toString() } }); } return value; } }); } static setBaggage(baggage, ctx = context.active()) { const currentBaggage = Object.fromEntries(propagation.getBaggage(ctx)?.getAllEntries() ?? []); const newCtx = propagation.setBaggage( ctx, propagation.createBaggage({ ...currentBaggage, ...baggage }) ); return newCtx; } static withContext(ctx, fn) { return context.with(ctx, fn); } /** * method to trace individual methods with proper context * @param method The method to trace * @param context Additional context for the trace * @returns Wrapped method with tracing */ traceMethod(method, context3) { let ctx = context.active(); const { skipIfNoTelemetry = true } = context3; if (skipIfNoTelemetry && !hasActiveTelemetry()) { return method; } return ((...args) => { const span = this.tracer.startSpan(context3.spanName); function handleError(error) { span.recordException(error); span.setStatus({ code: SpanStatusCode.ERROR, message: error.message }); span.end(); throw error; } try { let recordResult2 = function(res) { try { span.setAttribute(`${context3.spanName}.result`, JSON.stringify(res)); } catch { span.setAttribute(`${context3.spanName}.result`, "[Not Serializable]"); } span.end(); return res; }; const { requestId, componentName, runId, threadId, resourceId } = getBaggageValues(ctx); if (context3.attributes) { span.setAttributes(context3.attributes); } if (requestId) { span.setAttribute("http.request_id", requestId); } if (threadId) { span.setAttribute("threadId", threadId); } if (resourceId) { span.setAttribute("resourceId", resourceId); } if (context3.attributes?.componentName) { ctx = propagation.setBaggage( ctx, propagation.createBaggage({ componentName: { value: context3.attributes.componentName }, // @ts-ignore runId: { value: context3.attributes.runId }, // @ts-ignore "http.request_id": { value: requestId } }) ); } else { if (componentName) { span.setAttribute("componentName", componentName); span.setAttribute("runId", runId); } else if (this && this.name) { span.setAttribute("componentName", this.name); span.setAttribute("runId", this.runId); ctx = propagation.setBaggage( ctx, propagation.createBaggage({ componentName: { value: this.name }, // @ts-ignore runId: { value: this.runId }, // @ts-ignore "http.request_id": { value: requestId }, // @ts-ignore threadId: { value: threadId }, // @ts-ignore resourceId: { value: resourceId } }) ); } } args.forEach((arg, index) => { try { span.setAttribute(`${context3.spanName}.argument.${index}`, JSON.stringify(arg)); } catch { span.setAttribute(`${context3.spanName}.argument.${index}`, "[Not Serializable]"); } }); let result; context.with(trace.setSpan(ctx, span), () => { result = method(...args); }); if (result instanceof Promise) { return result.then(recordResult2).catch(handleError); } else { return recordResult2(result); } } catch (error) { handleError(error); } }); } getBaggageTracer() { return new BaggageTracer(this.tracer); } }; var BaggageTracer = class { _tracer; constructor(tracer) { this._tracer = tracer; } startSpan(name, options = {}, ctx) { ctx = ctx ?? context.active(); const span = this._tracer.startSpan(name, options, ctx); const { componentName, runId, requestId, threadId, resourceId } = getBaggageValues(ctx); span.setAttribute("componentName", componentName); span.setAttribute("runId", runId); span.setAttribute("http.request_id", requestId); span.setAttribute("threadId", threadId); span.setAttribute("resourceId", resourceId); return span; } startActiveSpan(name, optionsOrFn, ctxOrFn, fn) { if (typeof optionsOrFn === "function") { const wrappedFn2 = (span) => { const { componentName, runId, requestId, threadId, resourceId } = getBaggageValues(context.active()); span.setAttribute("componentName", componentName); span.setAttribute("runId", runId); span.setAttribute("http.request_id", requestId); span.setAttribute("threadId", threadId); span.setAttribute("resourceId", resourceId); return optionsOrFn(span); }; return this._tracer.startActiveSpan(name, {}, context.active(), wrappedFn2); } if (typeof ctxOrFn === "function") { const wrappedFn2 = (span) => { const { componentName, runId, requestId, threadId, resourceId } = getBaggageValues(context.active()); span.setAttribute("componentName", componentName); span.setAttribute("runId", runId); span.setAttribute("http.request_id", requestId); span.setAttribute("threadId", threadId); span.setAttribute("resourceId", resourceId); return ctxOrFn(span); }; return this._tracer.startActiveSpan(name, optionsOrFn, context.active(), wrappedFn2); } const wrappedFn = (span) => { const { componentName, runId, requestId, threadId, resourceId } = getBaggageValues( ctxOrFn ?? context.active() ); span.setAttribute("componentName", componentName); span.setAttribute("runId", runId); span.setAttribute("http.request_id", requestId); span.setAttribute("threadId", threadId); span.setAttribute("resourceId", resourceId); return fn(span); }; return this._tracer.startActiveSpan(name, optionsOrFn, ctxOrFn, wrappedFn); } }; export { InstrumentClass, OTLPTraceExporter, Telemetry, getBaggageValues, hasActiveTelemetry, withSpan }; //# sourceMappingURL=chunk-SEZBEL3U.js.map //# sourceMappingURL=chunk-SEZBEL3U.js.map