UNPKG

@sentry/node

Version:

Sentry Node SDK using OpenTelemetry for performance instrumentation

171 lines (168 loc) 6.49 kB
import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation'; import { SDK_VERSION, getClient, handleCallbackErrors, addNonEnumerableProperty, getActiveSpan, _INTERNAL_getSpanContextForToolCallId, withScope, captureException, _INTERNAL_cleanupToolCallSpanContext } from '@sentry/core'; import { INTEGRATION_NAME } from './constants.js'; const SUPPORTED_VERSIONS = [">=3.0.0 <7"]; const INSTRUMENTED_METHODS = [ "generateText", "streamText", "generateObject", "streamObject", "embed", "embedMany", "rerank" ]; function isToolError(obj) { if (typeof obj !== "object" || obj === null) { return false; } const candidate = obj; return "type" in candidate && "error" in candidate && "toolName" in candidate && "toolCallId" in candidate && candidate.type === "tool-error" && candidate.error instanceof Error; } function processToolCallResults(result) { if (typeof result !== "object" || result === null || !("content" in result)) { return; } const resultObj = result; if (!Array.isArray(resultObj.content)) { return; } captureToolErrors(resultObj.content); cleanupToolCallSpanContexts(resultObj.content); } function captureToolErrors(content) { for (const item of content) { if (!isToolError(item)) { continue; } const spanContext = _INTERNAL_getSpanContextForToolCallId(item.toolCallId); if (spanContext) { withScope((scope) => { scope.setContext("trace", { trace_id: spanContext.traceId, span_id: spanContext.spanId }); scope.setTag("vercel.ai.tool.name", item.toolName); scope.setTag("vercel.ai.tool.callId", item.toolCallId); scope.setLevel("error"); captureException(item.error, { mechanism: { type: "auto.vercelai.otel", handled: false } }); }); } else { withScope((scope) => { scope.setTag("vercel.ai.tool.name", item.toolName); scope.setTag("vercel.ai.tool.callId", item.toolCallId); scope.setLevel("error"); captureException(item.error, { mechanism: { type: "auto.vercelai.otel", handled: false } }); }); } } } function cleanupToolCallSpanContexts(content) { for (const item of content) { if (typeof item === "object" && item !== null && "toolCallId" in item && typeof item.toolCallId === "string") { _INTERNAL_cleanupToolCallSpanContext(item.toolCallId); } } } function determineRecordingSettings(integrationRecordingOptions, methodTelemetryOptions, telemetryExplicitlyEnabled, defaultRecordingEnabled) { const recordInputs = integrationRecordingOptions?.recordInputs !== void 0 ? integrationRecordingOptions.recordInputs : methodTelemetryOptions.recordInputs !== void 0 ? methodTelemetryOptions.recordInputs : telemetryExplicitlyEnabled === true ? true : defaultRecordingEnabled; const recordOutputs = integrationRecordingOptions?.recordOutputs !== void 0 ? integrationRecordingOptions.recordOutputs : methodTelemetryOptions.recordOutputs !== void 0 ? methodTelemetryOptions.recordOutputs : telemetryExplicitlyEnabled === true ? true : defaultRecordingEnabled; return { recordInputs, recordOutputs }; } class SentryVercelAiInstrumentation extends InstrumentationBase { constructor(config = {}) { super("@sentry/instrumentation-vercel-ai", SDK_VERSION, config); this._isPatched = false; this._callbacks = []; } /** * Initializes the instrumentation by defining the modules to be patched. */ init() { const module = new InstrumentationNodeModuleDefinition("ai", SUPPORTED_VERSIONS, this._patch.bind(this)); return module; } /** * Call the provided callback when the module is patched. * If it has already been patched, the callback will be called immediately. */ callWhenPatched(callback) { if (this._isPatched) { callback(); } else { this._callbacks.push(callback); } } /** * Patches module exports to enable Vercel AI telemetry. */ _patch(moduleExports) { this._isPatched = true; this._callbacks.forEach((callback) => callback()); this._callbacks = []; const generatePatch = (originalMethod) => { return new Proxy(originalMethod, { apply: (target, thisArg, args) => { const existingExperimentalTelemetry = args[0].experimental_telemetry || {}; const isEnabled = existingExperimentalTelemetry.isEnabled; const client = getClient(); const integration = client?.getIntegrationByName(INTEGRATION_NAME); const integrationOptions = integration?.options; const shouldRecordInputsAndOutputs = integration ? Boolean(client?.getOptions().sendDefaultPii) : false; const { recordInputs, recordOutputs } = determineRecordingSettings( integrationOptions, existingExperimentalTelemetry, isEnabled, shouldRecordInputsAndOutputs ); args[0].experimental_telemetry = { ...existingExperimentalTelemetry, isEnabled: isEnabled !== void 0 ? isEnabled : true, recordInputs, recordOutputs }; return handleCallbackErrors( () => Reflect.apply(target, thisArg, args), (error) => { if (error && typeof error === "object") { addNonEnumerableProperty(error, "_sentry_active_span", getActiveSpan()); } }, () => { }, (result) => { processToolCallResults(result); } ); } }); }; if (Object.prototype.toString.call(moduleExports) === "[object Module]") { for (const method of INSTRUMENTED_METHODS) { if (moduleExports[method] != null) { moduleExports[method] = generatePatch(moduleExports[method]); } } return moduleExports; } else { const patchedModuleExports = INSTRUMENTED_METHODS.reduce((acc, curr) => { if (moduleExports[curr] != null) { acc[curr] = generatePatch(moduleExports[curr]); } return acc; }, {}); return { ...moduleExports, ...patchedModuleExports }; } } } export { SentryVercelAiInstrumentation, cleanupToolCallSpanContexts, determineRecordingSettings, processToolCallResults }; //# sourceMappingURL=instrumentation.js.map