@mastra/sentry 1.2.5 → 1.2.6
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/CHANGELOG.md +18 -0
- package/dist/index.cjs +483 -501
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +458 -478
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
package/dist/index.cjs
CHANGED
|
@@ -1,506 +1,488 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
n.default = e;
|
|
23
|
-
return Object.freeze(n);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
var Sentry__namespace = /*#__PURE__*/_interopNamespace(Sentry);
|
|
27
|
-
|
|
28
|
-
// src/tracing.ts
|
|
29
|
-
var SPAN_TYPE_CONFIG = {
|
|
30
|
-
[observability$1.SpanType.AGENT_RUN]: { opType: "gen_ai.invoke_agent", opName: "invoke_agent" },
|
|
31
|
-
[observability$1.SpanType.MODEL_GENERATION]: { opType: "gen_ai.chat", opName: "chat" },
|
|
32
|
-
[observability$1.SpanType.TOOL_CALL]: { opType: "gen_ai.execute_tool", opName: "execute_tool" },
|
|
33
|
-
[observability$1.SpanType.MCP_TOOL_CALL]: { opType: "gen_ai.execute_tool", opName: "execute_tool" },
|
|
34
|
-
[observability$1.SpanType.PROVIDER_TOOL_CALL]: { opType: "gen_ai.execute_tool", opName: "execute_tool" },
|
|
35
|
-
[observability$1.SpanType.WORKFLOW_RUN]: { opType: "workflow.run", opName: "workflow" },
|
|
36
|
-
[observability$1.SpanType.WORKFLOW_STEP]: { opType: "workflow.step", opName: "step" },
|
|
37
|
-
[observability$1.SpanType.WORKFLOW_CONDITIONAL]: { opType: "workflow.conditional", opName: "step" },
|
|
38
|
-
[observability$1.SpanType.WORKFLOW_CONDITIONAL_EVAL]: { opType: "workflow.conditional", opName: "step" },
|
|
39
|
-
[observability$1.SpanType.WORKFLOW_PARALLEL]: { opType: "workflow.parallel", opName: "step" },
|
|
40
|
-
[observability$1.SpanType.WORKFLOW_LOOP]: { opType: "workflow.loop", opName: "step" },
|
|
41
|
-
[observability$1.SpanType.WORKFLOW_SLEEP]: { opType: "workflow.sleep", opName: "step" },
|
|
42
|
-
[observability$1.SpanType.WORKFLOW_WAIT_EVENT]: { opType: "workflow.wait", opName: "step" },
|
|
43
|
-
[observability$1.SpanType.PROCESSOR_RUN]: { opType: "ai.processor", opName: "step" },
|
|
44
|
-
[observability$1.SpanType.GENERIC]: { opType: "ai.span", opName: "span" },
|
|
45
|
-
[observability$1.SpanType.MODEL_STEP]: { opType: "ai.span", opName: "step" },
|
|
46
|
-
[observability$1.SpanType.MODEL_CHUNK]: { opType: "ai.span", opName: "step" },
|
|
47
|
-
[observability$1.SpanType.SCORER_RUN]: { opType: "workflow.run", opName: "eval" },
|
|
48
|
-
[observability$1.SpanType.SCORER_STEP]: { opType: "workflow.step", opName: "step" },
|
|
49
|
-
[observability$1.SpanType.MEMORY_OPERATION]: { opType: "ai.memory", opName: "memory" }
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
50
18
|
};
|
|
51
|
-
var
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
let _mastra_core_observability = require("@mastra/core/observability");
|
|
25
|
+
let _mastra_observability = require("@mastra/observability");
|
|
26
|
+
let _mastra_otel_exporter = require("@mastra/otel-exporter");
|
|
27
|
+
let _sentry_node = require("@sentry/node");
|
|
28
|
+
_sentry_node = __toESM(_sentry_node, 1);
|
|
29
|
+
//#region src/tracing.ts
|
|
30
|
+
const SPAN_TYPE_CONFIG = {
|
|
31
|
+
[_mastra_core_observability.SpanType.AGENT_RUN]: {
|
|
32
|
+
opType: "gen_ai.invoke_agent",
|
|
33
|
+
opName: "invoke_agent"
|
|
34
|
+
},
|
|
35
|
+
[_mastra_core_observability.SpanType.MODEL_GENERATION]: {
|
|
36
|
+
opType: "gen_ai.chat",
|
|
37
|
+
opName: "chat"
|
|
38
|
+
},
|
|
39
|
+
[_mastra_core_observability.SpanType.TOOL_CALL]: {
|
|
40
|
+
opType: "gen_ai.execute_tool",
|
|
41
|
+
opName: "execute_tool"
|
|
42
|
+
},
|
|
43
|
+
[_mastra_core_observability.SpanType.MCP_TOOL_CALL]: {
|
|
44
|
+
opType: "gen_ai.execute_tool",
|
|
45
|
+
opName: "execute_tool"
|
|
46
|
+
},
|
|
47
|
+
[_mastra_core_observability.SpanType.PROVIDER_TOOL_CALL]: {
|
|
48
|
+
opType: "gen_ai.execute_tool",
|
|
49
|
+
opName: "execute_tool"
|
|
50
|
+
},
|
|
51
|
+
[_mastra_core_observability.SpanType.WORKFLOW_RUN]: {
|
|
52
|
+
opType: "workflow.run",
|
|
53
|
+
opName: "workflow"
|
|
54
|
+
},
|
|
55
|
+
[_mastra_core_observability.SpanType.WORKFLOW_STEP]: {
|
|
56
|
+
opType: "workflow.step",
|
|
57
|
+
opName: "step"
|
|
58
|
+
},
|
|
59
|
+
[_mastra_core_observability.SpanType.WORKFLOW_CONDITIONAL]: {
|
|
60
|
+
opType: "workflow.conditional",
|
|
61
|
+
opName: "step"
|
|
62
|
+
},
|
|
63
|
+
[_mastra_core_observability.SpanType.WORKFLOW_CONDITIONAL_EVAL]: {
|
|
64
|
+
opType: "workflow.conditional",
|
|
65
|
+
opName: "step"
|
|
66
|
+
},
|
|
67
|
+
[_mastra_core_observability.SpanType.WORKFLOW_PARALLEL]: {
|
|
68
|
+
opType: "workflow.parallel",
|
|
69
|
+
opName: "step"
|
|
70
|
+
},
|
|
71
|
+
[_mastra_core_observability.SpanType.WORKFLOW_LOOP]: {
|
|
72
|
+
opType: "workflow.loop",
|
|
73
|
+
opName: "step"
|
|
74
|
+
},
|
|
75
|
+
[_mastra_core_observability.SpanType.WORKFLOW_SLEEP]: {
|
|
76
|
+
opType: "workflow.sleep",
|
|
77
|
+
opName: "step"
|
|
78
|
+
},
|
|
79
|
+
[_mastra_core_observability.SpanType.WORKFLOW_WAIT_EVENT]: {
|
|
80
|
+
opType: "workflow.wait",
|
|
81
|
+
opName: "step"
|
|
82
|
+
},
|
|
83
|
+
[_mastra_core_observability.SpanType.PROCESSOR_RUN]: {
|
|
84
|
+
opType: "ai.processor",
|
|
85
|
+
opName: "step"
|
|
86
|
+
},
|
|
87
|
+
[_mastra_core_observability.SpanType.GENERIC]: {
|
|
88
|
+
opType: "ai.span",
|
|
89
|
+
opName: "span"
|
|
90
|
+
},
|
|
91
|
+
[_mastra_core_observability.SpanType.MODEL_STEP]: {
|
|
92
|
+
opType: "ai.span",
|
|
93
|
+
opName: "step"
|
|
94
|
+
},
|
|
95
|
+
[_mastra_core_observability.SpanType.MODEL_CHUNK]: {
|
|
96
|
+
opType: "ai.span",
|
|
97
|
+
opName: "step"
|
|
98
|
+
},
|
|
99
|
+
[_mastra_core_observability.SpanType.SCORER_RUN]: {
|
|
100
|
+
opType: "workflow.run",
|
|
101
|
+
opName: "eval"
|
|
102
|
+
},
|
|
103
|
+
[_mastra_core_observability.SpanType.SCORER_STEP]: {
|
|
104
|
+
opType: "workflow.step",
|
|
105
|
+
opName: "step"
|
|
106
|
+
},
|
|
107
|
+
[_mastra_core_observability.SpanType.MEMORY_OPERATION]: {
|
|
108
|
+
opType: "ai.memory",
|
|
109
|
+
opName: "memory"
|
|
110
|
+
}
|
|
78
111
|
};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
...config.options
|
|
107
|
-
});
|
|
108
|
-
this.initialized = true;
|
|
109
|
-
} catch (error) {
|
|
110
|
-
this.setDisabled(`Failed to initialize Sentry: ${error}`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
// ============================================================================
|
|
114
|
-
// Main Event Handlers
|
|
115
|
-
// ============================================================================
|
|
116
|
-
async _exportTracingEvent(event) {
|
|
117
|
-
if (!this.initialized) return;
|
|
118
|
-
const { type, exportedSpan } = event;
|
|
119
|
-
if (exportedSpan.isEvent) {
|
|
120
|
-
this.handleEventSpan(exportedSpan);
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
if (exportedSpan.type === observability$1.SpanType.MODEL_CHUNK || exportedSpan.type === observability$1.SpanType.MODEL_STEP) {
|
|
124
|
-
if (type === observability$1.TracingEventType.SPAN_STARTED) {
|
|
125
|
-
this.skippedSpans.set(exportedSpan.id, exportedSpan.parentSpanId || "");
|
|
126
|
-
} else if (type === observability$1.TracingEventType.SPAN_ENDED) {
|
|
127
|
-
this.skippedSpans.delete(exportedSpan.id);
|
|
128
|
-
}
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
switch (type) {
|
|
132
|
-
case observability$1.TracingEventType.SPAN_STARTED:
|
|
133
|
-
await this.handleSpanStarted(exportedSpan);
|
|
134
|
-
break;
|
|
135
|
-
case observability$1.TracingEventType.SPAN_UPDATED:
|
|
136
|
-
await this.handleSpanUpdated(exportedSpan);
|
|
137
|
-
break;
|
|
138
|
-
case observability$1.TracingEventType.SPAN_ENDED:
|
|
139
|
-
await this.handleSpanEnded(exportedSpan);
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
handleEventSpan(span) {
|
|
144
|
-
Sentry__namespace.addBreadcrumb({
|
|
145
|
-
type: "default",
|
|
146
|
-
category: span.type,
|
|
147
|
-
message: span.name,
|
|
148
|
-
level: span.errorInfo ? "error" : "info",
|
|
149
|
-
data: {
|
|
150
|
-
spanId: span.id,
|
|
151
|
-
traceId: span.traceId,
|
|
152
|
-
...span.input && { input: this.serializeValue(span.input) },
|
|
153
|
-
...span.output && { output: this.serializeValue(span.output) },
|
|
154
|
-
...span.metadata && { metadata: span.metadata },
|
|
155
|
-
...span.attributes && { attributes: span.attributes }
|
|
156
|
-
},
|
|
157
|
-
timestamp: span.startTime.getTime() / 1e3
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
async handleSpanStarted(span) {
|
|
161
|
-
const resolvedParentId = this.resolveParentSpanId(span.parentSpanId);
|
|
162
|
-
const sentrySpan = Sentry__namespace.startInactiveSpan({
|
|
163
|
-
op: this.getOperationType(span),
|
|
164
|
-
name: otelExporter.getSpanName(span),
|
|
165
|
-
startTime: span.startTime.getTime(),
|
|
166
|
-
forceTransaction: span.isRootSpan,
|
|
167
|
-
parentSpan: resolvedParentId ? this.spanMap.get(resolvedParentId)?.span : void 0
|
|
168
|
-
});
|
|
169
|
-
sentrySpan.setAttributes(this.buildSpanAttributes(span));
|
|
170
|
-
this.spanMap.set(span.id, {
|
|
171
|
-
span: sentrySpan,
|
|
172
|
-
spanType: span.type
|
|
173
|
-
});
|
|
174
|
-
if ((span.type === observability$1.SpanType.TOOL_CALL || span.type === observability$1.SpanType.PROVIDER_TOOL_CALL) && resolvedParentId) {
|
|
175
|
-
this.trackToolCallForParent(span, resolvedParentId);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
async handleSpanUpdated(span) {
|
|
179
|
-
const spanData = this.spanMap.get(span.id);
|
|
180
|
-
if (!spanData) {
|
|
181
|
-
this.logMissingSpan(span, "span update");
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
async handleSpanEnded(span) {
|
|
186
|
-
const spanData = this.spanMap.get(span.id);
|
|
187
|
-
if (!spanData) {
|
|
188
|
-
this.logMissingSpan(span, "span end");
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
const { span: sentrySpan } = spanData;
|
|
192
|
-
sentrySpan.setAttributes(this.buildSpanAttributes(span));
|
|
193
|
-
if (span.type === observability$1.SpanType.MODEL_GENERATION) {
|
|
194
|
-
this.applyToolCallsAttribute(spanData);
|
|
195
|
-
const resolvedParentId = this.resolveParentSpanId(span.parentSpanId);
|
|
196
|
-
if (resolvedParentId) {
|
|
197
|
-
const parentData = this.spanMap.get(resolvedParentId);
|
|
198
|
-
if (parentData?.spanType === observability$1.SpanType.AGENT_RUN) {
|
|
199
|
-
const modelAttr = span.attributes;
|
|
200
|
-
parentData.generation = {
|
|
201
|
-
model: modelAttr.model,
|
|
202
|
-
output: span.output,
|
|
203
|
-
usage: modelAttr.usage
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
if (span.type === observability$1.SpanType.AGENT_RUN) {
|
|
209
|
-
this.applyUsageFromGeneration(spanData);
|
|
210
|
-
this.setGenerationResponseAttributes(spanData);
|
|
211
|
-
}
|
|
212
|
-
if (span.errorInfo) {
|
|
213
|
-
sentrySpan.setStatus({
|
|
214
|
-
code: 2,
|
|
215
|
-
message: span.errorInfo.message
|
|
216
|
-
});
|
|
217
|
-
const error = new Error(span.errorInfo.message);
|
|
218
|
-
if (span.errorInfo.name) {
|
|
219
|
-
error.name = span.errorInfo.name;
|
|
220
|
-
}
|
|
221
|
-
if (span.errorInfo.stack) {
|
|
222
|
-
error.stack = span.errorInfo.stack;
|
|
223
|
-
}
|
|
224
|
-
Sentry__namespace.captureException(error, {
|
|
225
|
-
contexts: {
|
|
226
|
-
trace: { trace_id: span.traceId, span_id: span.id },
|
|
227
|
-
span_info: {
|
|
228
|
-
name: span.name,
|
|
229
|
-
type: span.type,
|
|
230
|
-
error_id: span.errorInfo.id,
|
|
231
|
-
error_category: span.errorInfo.category
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
const endTime = span.endTime ? span.endTime.getTime() : void 0;
|
|
237
|
-
sentrySpan.end(endTime);
|
|
238
|
-
this.spanMap.delete(span.id);
|
|
239
|
-
}
|
|
240
|
-
// ============================================================================
|
|
241
|
-
// Span Creation Helpers
|
|
242
|
-
// ============================================================================
|
|
243
|
-
resolveParentSpanId(parentSpanId) {
|
|
244
|
-
if (!parentSpanId) return void 0;
|
|
245
|
-
let currentParentId = parentSpanId;
|
|
246
|
-
while (currentParentId && this.skippedSpans.has(currentParentId)) {
|
|
247
|
-
currentParentId = this.skippedSpans.get(currentParentId);
|
|
248
|
-
if (!currentParentId) break;
|
|
249
|
-
}
|
|
250
|
-
return currentParentId;
|
|
251
|
-
}
|
|
252
|
-
getOperationType(span) {
|
|
253
|
-
const config = SPAN_TYPE_CONFIG[span.type];
|
|
254
|
-
return config ? config.opType : "ai.span";
|
|
255
|
-
}
|
|
256
|
-
buildSpanAttributes(span) {
|
|
257
|
-
const attributes = otelExporter.getAttributes(span);
|
|
258
|
-
attributes[ATTRIBUTE_KEYS.SPAN_TYPE] = span.type;
|
|
259
|
-
attributes[ATTRIBUTE_KEYS.ORIGIN] = "auto.ai.mastra";
|
|
260
|
-
if (span.metadata) {
|
|
261
|
-
Object.entries(span.metadata).forEach(([key, value]) => {
|
|
262
|
-
if (value !== void 0 && value !== null && key !== "langfuse") {
|
|
263
|
-
attributes[`metadata.${key}`] = this.serializeValue(value);
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.TAGS, span.tags?.join(","));
|
|
268
|
-
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.GEN_AI_CONVERSATION_ID, span.metadata?.threadId);
|
|
269
|
-
this.addInputOutputAttributes(attributes, span);
|
|
270
|
-
if (span.type === observability$1.SpanType.MODEL_GENERATION) {
|
|
271
|
-
this.addModelGenerationAttributes(attributes, span);
|
|
272
|
-
}
|
|
273
|
-
if (span.type === observability$1.SpanType.TOOL_CALL || span.type === observability$1.SpanType.PROVIDER_TOOL_CALL) {
|
|
274
|
-
this.addToolCallAttributes(attributes, span);
|
|
275
|
-
}
|
|
276
|
-
if (span.type === observability$1.SpanType.AGENT_RUN) {
|
|
277
|
-
this.addAgentRunAttributes(attributes, span);
|
|
278
|
-
}
|
|
279
|
-
if (span.type === observability$1.SpanType.WORKFLOW_RUN) {
|
|
280
|
-
const workflowAttr = span.attributes;
|
|
281
|
-
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.WORKFLOW_ID, this.getEntityName(span));
|
|
282
|
-
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.WORKFLOW_STATUS, workflowAttr.status);
|
|
283
|
-
}
|
|
284
|
-
if (span.type === observability$1.SpanType.WORKFLOW_STEP) {
|
|
285
|
-
const stepAttr = span.attributes;
|
|
286
|
-
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.WORKFLOW_STEP_ID, this.getEntityName(span));
|
|
287
|
-
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.WORKFLOW_STEP_STATUS, stepAttr.status);
|
|
288
|
-
}
|
|
289
|
-
return attributes;
|
|
290
|
-
}
|
|
291
|
-
// ============================================================================
|
|
292
|
-
// Sentry-Specific Attribute Formatters
|
|
293
|
-
// ============================================================================
|
|
294
|
-
/**
|
|
295
|
-
* Adds Sentry-specific input/output attributes that complement GenAI semantic conventions.
|
|
296
|
-
* Adds 'input' and 'output' keys for Sentry UI compatibility.
|
|
297
|
-
*/
|
|
298
|
-
addInputOutputAttributes(attributes, span) {
|
|
299
|
-
if (span.input !== void 0) {
|
|
300
|
-
attributes[ATTRIBUTE_KEYS.INPUT] = this.serializeValue(span.input);
|
|
301
|
-
}
|
|
302
|
-
if (span.output !== void 0) {
|
|
303
|
-
attributes[ATTRIBUTE_KEYS.OUTPUT] = this.serializeValue(span.output);
|
|
304
|
-
if (span.type === observability$1.SpanType.MODEL_GENERATION) {
|
|
305
|
-
const outputText = this.extractOutputText(span.output);
|
|
306
|
-
if (outputText) {
|
|
307
|
-
attributes[ATTRIBUTE_KEYS.GEN_AI_RESPONSE_TEXT] = outputText;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Adds Sentry-specific MODEL_GENERATION attributes that complement GenAI semantic conventions.
|
|
314
|
-
*/
|
|
315
|
-
addModelGenerationAttributes(attributes, span) {
|
|
316
|
-
const modelAttr = span.attributes;
|
|
317
|
-
if (modelAttr.streaming !== void 0) {
|
|
318
|
-
attributes[ATTRIBUTE_KEYS.GEN_AI_REQUEST_STREAM] = modelAttr.streaming;
|
|
319
|
-
attributes[ATTRIBUTE_KEYS.GEN_AI_RESPONSE_STREAMING] = modelAttr.streaming;
|
|
320
|
-
}
|
|
321
|
-
this.setAttributeIfDefined(
|
|
322
|
-
attributes,
|
|
323
|
-
ATTRIBUTE_KEYS.GEN_AI_COMPLETION_START_TIME,
|
|
324
|
-
modelAttr.completionStartTime?.toISOString()
|
|
325
|
-
);
|
|
326
|
-
if (modelAttr.usage) {
|
|
327
|
-
const totalTokens = (modelAttr.usage.inputTokens || 0) + (modelAttr.usage.outputTokens || 0);
|
|
328
|
-
if (totalTokens > 0) {
|
|
329
|
-
attributes[ATTRIBUTE_KEYS.GEN_AI_USAGE_TOTAL_TOKENS] = totalTokens;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* Adds Sentry-specific TOOL_CALL attributes that complement GenAI semantic conventions.
|
|
335
|
-
*/
|
|
336
|
-
addToolCallAttributes(attributes, span) {
|
|
337
|
-
const toolAttr = span.attributes;
|
|
338
|
-
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.TOOL_SUCCESS, toolAttr.success);
|
|
339
|
-
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.GEN_AI_TOOL_CALL_ID, span.metadata?.toolCallId);
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Adds Sentry-specific AGENT_RUN attributes that complement GenAI semantic conventions.
|
|
343
|
-
*/
|
|
344
|
-
addAgentRunAttributes(attributes, span) {
|
|
345
|
-
const agentAttr = span.attributes;
|
|
346
|
-
const agentName = this.getEntityName(span);
|
|
347
|
-
if (agentName) {
|
|
348
|
-
attributes[ATTRIBUTE_KEYS.GEN_AI_PIPELINE_NAME] = agentName;
|
|
349
|
-
}
|
|
350
|
-
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.GEN_AI_AGENT_PROMPT, agentAttr.prompt);
|
|
351
|
-
}
|
|
352
|
-
// ============================================================================
|
|
353
|
-
// Token Usage Management
|
|
354
|
-
// ============================================================================
|
|
355
|
-
/**
|
|
356
|
-
* Applies token usage from the MODEL_GENERATION span to the AGENT_RUN span attributes.
|
|
357
|
-
* Reads usage directly from the generation field.
|
|
358
|
-
* Called when AGENT_RUN spans end to set gen_ai.usage.* attributes.
|
|
359
|
-
*/
|
|
360
|
-
applyUsageFromGeneration(spanData) {
|
|
361
|
-
const usage = spanData.generation?.usage;
|
|
362
|
-
if (!usage) return;
|
|
363
|
-
const inputTokens = usage.inputTokens || 0;
|
|
364
|
-
const outputTokens = usage.outputTokens || 0;
|
|
365
|
-
if (inputTokens > 0) {
|
|
366
|
-
spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_USAGE_INPUT_TOKENS, inputTokens);
|
|
367
|
-
}
|
|
368
|
-
if (outputTokens > 0) {
|
|
369
|
-
spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_USAGE_OUTPUT_TOKENS, outputTokens);
|
|
370
|
-
}
|
|
371
|
-
const totalTokens = inputTokens + outputTokens;
|
|
372
|
-
if (totalTokens > 0) {
|
|
373
|
-
spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_USAGE_TOTAL_TOKENS, totalTokens);
|
|
374
|
-
}
|
|
375
|
-
const cacheReadTokens = usage.inputDetails?.cacheRead || 0;
|
|
376
|
-
const cacheWriteTokens = usage.inputDetails?.cacheWrite || 0;
|
|
377
|
-
const reasoningTokens = usage.outputDetails?.reasoning || 0;
|
|
378
|
-
if (cacheReadTokens > 0) {
|
|
379
|
-
spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_USAGE_CACHE_READ_TOKENS, cacheReadTokens);
|
|
380
|
-
}
|
|
381
|
-
if (cacheWriteTokens > 0) {
|
|
382
|
-
spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_USAGE_CACHE_WRITE_TOKENS, cacheWriteTokens);
|
|
383
|
-
}
|
|
384
|
-
if (reasoningTokens > 0) {
|
|
385
|
-
spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_USAGE_REASONING_TOKENS, reasoningTokens);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* Sets gen_ai.response.model and gen_ai.response.text from the MODEL_GENERATION.
|
|
390
|
-
* Only applies to AGENT_RUN spans.
|
|
391
|
-
*/
|
|
392
|
-
setGenerationResponseAttributes(spanData) {
|
|
393
|
-
if (!spanData.generation) return;
|
|
394
|
-
if (spanData.generation.model) {
|
|
395
|
-
spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_RESPONSE_MODEL, spanData.generation.model);
|
|
396
|
-
}
|
|
397
|
-
if (spanData.generation.output) {
|
|
398
|
-
const outputText = this.extractOutputText(spanData.generation.output);
|
|
399
|
-
if (outputText) {
|
|
400
|
-
spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_RESPONSE_TEXT, outputText);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
/**
|
|
405
|
-
* Tracks a TOOL_CALL span as a child of its parent MODEL_GENERATION span.
|
|
406
|
-
* This builds the tool_calls array for gen_ai.response.tool_calls attribute.
|
|
407
|
-
*/
|
|
408
|
-
trackToolCallForParent(span, parentId) {
|
|
409
|
-
const parentSpanData = this.spanMap.get(parentId);
|
|
410
|
-
if (!parentSpanData || parentSpanData.spanType !== observability$1.SpanType.MODEL_GENERATION) {
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
const toolAttr = span.attributes;
|
|
414
|
-
if (!parentSpanData.toolCalls) {
|
|
415
|
-
parentSpanData.toolCalls = [];
|
|
416
|
-
}
|
|
417
|
-
parentSpanData.toolCalls.push({
|
|
418
|
-
name: this.getEntityName(span),
|
|
419
|
-
id: span.metadata?.toolCallId,
|
|
420
|
-
type: toolAttr.toolType || "function"
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
/**
|
|
424
|
-
* Applies the gen_ai.response.tool_calls attribute to MODEL_GENERATION spans.
|
|
425
|
-
* Called when MODEL_GENERATION spans end if they have child tool calls.
|
|
426
|
-
*/
|
|
427
|
-
applyToolCallsAttribute(spanData) {
|
|
428
|
-
if (!spanData.toolCalls || spanData.toolCalls.length === 0) {
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_RESPONSE_TOOL_CALLS, JSON.stringify(spanData.toolCalls));
|
|
432
|
-
}
|
|
433
|
-
// ============================================================================
|
|
434
|
-
// Utility Helpers
|
|
435
|
-
// ============================================================================
|
|
436
|
-
logMissingSpan(span, operation) {
|
|
437
|
-
this.logger.warn(`Sentry exporter: No Sentry span found for ${operation}`, {
|
|
438
|
-
traceId: span.traceId,
|
|
439
|
-
spanId: span.id,
|
|
440
|
-
spanName: span.name
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
getEntityName(span) {
|
|
444
|
-
return span.entityName || span.entityId || "unknown";
|
|
445
|
-
}
|
|
446
|
-
extractOutputText(output) {
|
|
447
|
-
if (!output) return void 0;
|
|
448
|
-
if (typeof output === "string") return output;
|
|
449
|
-
if (output.text && typeof output.text === "string") return output.text;
|
|
450
|
-
if (output.content && typeof output.content === "string") return output.content;
|
|
451
|
-
if (output.message?.content && typeof output.message.content === "string") return output.message.content;
|
|
452
|
-
return void 0;
|
|
453
|
-
}
|
|
454
|
-
serializeValue(value) {
|
|
455
|
-
if (value === null || value === void 0) return value;
|
|
456
|
-
if (typeof value === "object") {
|
|
457
|
-
try {
|
|
458
|
-
return JSON.stringify(value);
|
|
459
|
-
} catch {
|
|
460
|
-
return String(value);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
return value;
|
|
464
|
-
}
|
|
465
|
-
setAttributeIfDefined(attributes, key, value) {
|
|
466
|
-
if (value !== void 0 && value !== null) {
|
|
467
|
-
attributes[key] = value;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
// ============================================================================
|
|
471
|
-
// Flush and Shutdown
|
|
472
|
-
// ============================================================================
|
|
473
|
-
/**
|
|
474
|
-
* Force flush any buffered spans without shutting down the exporter.
|
|
475
|
-
* This is useful in serverless environments where you need to ensure spans
|
|
476
|
-
* are exported before the runtime instance is terminated.
|
|
477
|
-
*/
|
|
478
|
-
async flush() {
|
|
479
|
-
if (!this.initialized) return;
|
|
480
|
-
try {
|
|
481
|
-
await Sentry__namespace.flush(2e3);
|
|
482
|
-
this.logger.debug("Sentry exporter: Flushed pending events");
|
|
483
|
-
} catch (error) {
|
|
484
|
-
this.logger.error("Sentry exporter: Error flushing events", { error });
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
async shutdown() {
|
|
488
|
-
for (const [spanId, spanData] of this.spanMap.entries()) {
|
|
489
|
-
try {
|
|
490
|
-
spanData.span.end();
|
|
491
|
-
} catch (error) {
|
|
492
|
-
this.logger.error("Sentry exporter: Error ending span during shutdown", { spanId, error });
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
this.spanMap.clear();
|
|
496
|
-
this.skippedSpans.clear();
|
|
497
|
-
if (this.initialized) {
|
|
498
|
-
await Sentry__namespace.close(2e3);
|
|
499
|
-
}
|
|
500
|
-
await super.shutdown();
|
|
501
|
-
}
|
|
112
|
+
const ATTRIBUTE_KEYS = {
|
|
113
|
+
SPAN_TYPE: "ai.span.type",
|
|
114
|
+
ORIGIN: "sentry.origin",
|
|
115
|
+
TAGS: "tags",
|
|
116
|
+
INPUT: "input",
|
|
117
|
+
OUTPUT: "output",
|
|
118
|
+
GEN_AI_REQUEST_STREAM: "gen_ai.request.stream",
|
|
119
|
+
GEN_AI_RESPONSE_MODEL: "gen_ai.response.model",
|
|
120
|
+
GEN_AI_RESPONSE_STREAMING: "gen_ai.response.streaming",
|
|
121
|
+
GEN_AI_RESPONSE_TOOL_CALLS: "gen_ai.response.tool_calls",
|
|
122
|
+
GEN_AI_RESPONSE_TEXT: "gen_ai.response.text",
|
|
123
|
+
GEN_AI_CONVERSATION_ID: "gen_ai.conversation.id",
|
|
124
|
+
GEN_AI_COMPLETION_START_TIME: "gen_ai.completion_start_time",
|
|
125
|
+
GEN_AI_TOOL_CALL_ID: "gen_ai.tool.call.id",
|
|
126
|
+
TOOL_SUCCESS: "tool.success",
|
|
127
|
+
GEN_AI_PIPELINE_NAME: "gen_ai.pipeline.name",
|
|
128
|
+
GEN_AI_AGENT_PROMPT: "gen_ai.agent.prompt",
|
|
129
|
+
WORKFLOW_ID: "workflow.id",
|
|
130
|
+
WORKFLOW_STATUS: "workflow.status",
|
|
131
|
+
WORKFLOW_STEP_ID: "workflow.step.id",
|
|
132
|
+
WORKFLOW_STEP_STATUS: "workflow.step.status",
|
|
133
|
+
GEN_AI_USAGE_INPUT_TOKENS: "gen_ai.usage.input_tokens",
|
|
134
|
+
GEN_AI_USAGE_OUTPUT_TOKENS: "gen_ai.usage.output_tokens",
|
|
135
|
+
GEN_AI_USAGE_TOTAL_TOKENS: "gen_ai.usage.total_tokens",
|
|
136
|
+
GEN_AI_USAGE_CACHE_READ_TOKENS: "gen_ai.usage.cache_read.input_tokens",
|
|
137
|
+
GEN_AI_USAGE_CACHE_WRITE_TOKENS: "gen_ai.usage.cache_creation.input_tokens",
|
|
138
|
+
GEN_AI_USAGE_REASONING_TOKENS: "gen_ai.usage.reasoning_tokens"
|
|
502
139
|
};
|
|
503
|
-
|
|
140
|
+
var SentryExporter = class extends _mastra_observability.BaseExporter {
|
|
141
|
+
name = "sentry";
|
|
142
|
+
sentryConfig;
|
|
143
|
+
spanMap = /* @__PURE__ */ new Map();
|
|
144
|
+
skippedSpans = /* @__PURE__ */ new Map();
|
|
145
|
+
initialized = false;
|
|
146
|
+
constructor(config = {}) {
|
|
147
|
+
super(config);
|
|
148
|
+
this.sentryConfig = {
|
|
149
|
+
dsn: config.dsn ?? process.env.SENTRY_DSN ?? "",
|
|
150
|
+
environment: config.environment ?? process.env.SENTRY_ENVIRONMENT ?? "production",
|
|
151
|
+
tracesSampleRate: config.tracesSampleRate ?? 1,
|
|
152
|
+
release: config.release ?? process.env.SENTRY_RELEASE ?? ""
|
|
153
|
+
};
|
|
154
|
+
if (!this.sentryConfig.dsn) {
|
|
155
|
+
const dsnSource = config.dsn ? "from config" : process.env.SENTRY_DSN ? "from env" : "missing";
|
|
156
|
+
this.setDisabled(`Missing required DSN (dsn: ${dsnSource}). Set SENTRY_DSN environment variable or pass it in config.`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
_sentry_node.init({
|
|
161
|
+
dsn: this.sentryConfig.dsn,
|
|
162
|
+
environment: this.sentryConfig.environment,
|
|
163
|
+
tracesSampleRate: this.sentryConfig.tracesSampleRate,
|
|
164
|
+
release: this.sentryConfig.release,
|
|
165
|
+
...config.options
|
|
166
|
+
});
|
|
167
|
+
this.initialized = true;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
this.setDisabled(`Failed to initialize Sentry: ${error}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async _exportTracingEvent(event) {
|
|
173
|
+
if (!this.initialized) return;
|
|
174
|
+
const { type, exportedSpan } = event;
|
|
175
|
+
if (exportedSpan.isEvent) {
|
|
176
|
+
this.handleEventSpan(exportedSpan);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (exportedSpan.type === _mastra_core_observability.SpanType.MODEL_CHUNK || exportedSpan.type === _mastra_core_observability.SpanType.MODEL_STEP) {
|
|
180
|
+
if (type === _mastra_core_observability.TracingEventType.SPAN_STARTED) this.skippedSpans.set(exportedSpan.id, exportedSpan.parentSpanId || "");
|
|
181
|
+
else if (type === _mastra_core_observability.TracingEventType.SPAN_ENDED) this.skippedSpans.delete(exportedSpan.id);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
switch (type) {
|
|
185
|
+
case _mastra_core_observability.TracingEventType.SPAN_STARTED:
|
|
186
|
+
await this.handleSpanStarted(exportedSpan);
|
|
187
|
+
break;
|
|
188
|
+
case _mastra_core_observability.TracingEventType.SPAN_UPDATED:
|
|
189
|
+
await this.handleSpanUpdated(exportedSpan);
|
|
190
|
+
break;
|
|
191
|
+
case _mastra_core_observability.TracingEventType.SPAN_ENDED:
|
|
192
|
+
await this.handleSpanEnded(exportedSpan);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
handleEventSpan(span) {
|
|
197
|
+
_sentry_node.addBreadcrumb({
|
|
198
|
+
type: "default",
|
|
199
|
+
category: span.type,
|
|
200
|
+
message: span.name,
|
|
201
|
+
level: span.errorInfo ? "error" : "info",
|
|
202
|
+
data: {
|
|
203
|
+
spanId: span.id,
|
|
204
|
+
traceId: span.traceId,
|
|
205
|
+
...span.input && { input: this.serializeValue(span.input) },
|
|
206
|
+
...span.output && { output: this.serializeValue(span.output) },
|
|
207
|
+
...span.metadata && { metadata: span.metadata },
|
|
208
|
+
...span.attributes && { attributes: span.attributes }
|
|
209
|
+
},
|
|
210
|
+
timestamp: span.startTime.getTime() / 1e3
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async handleSpanStarted(span) {
|
|
214
|
+
const resolvedParentId = this.resolveParentSpanId(span.parentSpanId);
|
|
215
|
+
const sentrySpan = _sentry_node.startInactiveSpan({
|
|
216
|
+
op: this.getOperationType(span),
|
|
217
|
+
name: (0, _mastra_otel_exporter.getSpanName)(span),
|
|
218
|
+
startTime: span.startTime.getTime(),
|
|
219
|
+
forceTransaction: span.isRootSpan,
|
|
220
|
+
parentSpan: resolvedParentId ? this.spanMap.get(resolvedParentId)?.span : void 0
|
|
221
|
+
});
|
|
222
|
+
sentrySpan.setAttributes(this.buildSpanAttributes(span));
|
|
223
|
+
this.spanMap.set(span.id, {
|
|
224
|
+
span: sentrySpan,
|
|
225
|
+
spanType: span.type
|
|
226
|
+
});
|
|
227
|
+
if ((span.type === _mastra_core_observability.SpanType.TOOL_CALL || span.type === _mastra_core_observability.SpanType.PROVIDER_TOOL_CALL) && resolvedParentId) this.trackToolCallForParent(span, resolvedParentId);
|
|
228
|
+
}
|
|
229
|
+
async handleSpanUpdated(span) {
|
|
230
|
+
if (!this.spanMap.get(span.id)) {
|
|
231
|
+
this.logMissingSpan(span, "span update");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async handleSpanEnded(span) {
|
|
236
|
+
const spanData = this.spanMap.get(span.id);
|
|
237
|
+
if (!spanData) {
|
|
238
|
+
this.logMissingSpan(span, "span end");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const { span: sentrySpan } = spanData;
|
|
242
|
+
sentrySpan.setAttributes(this.buildSpanAttributes(span));
|
|
243
|
+
if (span.type === _mastra_core_observability.SpanType.MODEL_GENERATION) {
|
|
244
|
+
this.applyToolCallsAttribute(spanData);
|
|
245
|
+
const resolvedParentId = this.resolveParentSpanId(span.parentSpanId);
|
|
246
|
+
if (resolvedParentId) {
|
|
247
|
+
const parentData = this.spanMap.get(resolvedParentId);
|
|
248
|
+
if (parentData?.spanType === _mastra_core_observability.SpanType.AGENT_RUN) {
|
|
249
|
+
const modelAttr = span.attributes;
|
|
250
|
+
parentData.generation = {
|
|
251
|
+
model: modelAttr.model,
|
|
252
|
+
output: span.output,
|
|
253
|
+
usage: modelAttr.usage
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (span.type === _mastra_core_observability.SpanType.AGENT_RUN) {
|
|
259
|
+
this.applyUsageFromGeneration(spanData);
|
|
260
|
+
this.setGenerationResponseAttributes(spanData);
|
|
261
|
+
}
|
|
262
|
+
if (span.errorInfo) {
|
|
263
|
+
sentrySpan.setStatus({
|
|
264
|
+
code: 2,
|
|
265
|
+
message: span.errorInfo.message
|
|
266
|
+
});
|
|
267
|
+
const error = new Error(span.errorInfo.message);
|
|
268
|
+
if (span.errorInfo.name) error.name = span.errorInfo.name;
|
|
269
|
+
if (span.errorInfo.stack) error.stack = span.errorInfo.stack;
|
|
270
|
+
_sentry_node.captureException(error, { contexts: {
|
|
271
|
+
trace: {
|
|
272
|
+
trace_id: span.traceId,
|
|
273
|
+
span_id: span.id
|
|
274
|
+
},
|
|
275
|
+
span_info: {
|
|
276
|
+
name: span.name,
|
|
277
|
+
type: span.type,
|
|
278
|
+
error_id: span.errorInfo.id,
|
|
279
|
+
error_category: span.errorInfo.category
|
|
280
|
+
}
|
|
281
|
+
} });
|
|
282
|
+
}
|
|
283
|
+
const endTime = span.endTime ? span.endTime.getTime() : void 0;
|
|
284
|
+
sentrySpan.end(endTime);
|
|
285
|
+
this.spanMap.delete(span.id);
|
|
286
|
+
}
|
|
287
|
+
resolveParentSpanId(parentSpanId) {
|
|
288
|
+
if (!parentSpanId) return void 0;
|
|
289
|
+
let currentParentId = parentSpanId;
|
|
290
|
+
while (currentParentId && this.skippedSpans.has(currentParentId)) {
|
|
291
|
+
currentParentId = this.skippedSpans.get(currentParentId);
|
|
292
|
+
if (!currentParentId) break;
|
|
293
|
+
}
|
|
294
|
+
return currentParentId;
|
|
295
|
+
}
|
|
296
|
+
getOperationType(span) {
|
|
297
|
+
const config = SPAN_TYPE_CONFIG[span.type];
|
|
298
|
+
return config ? config.opType : "ai.span";
|
|
299
|
+
}
|
|
300
|
+
buildSpanAttributes(span) {
|
|
301
|
+
const attributes = (0, _mastra_otel_exporter.getAttributes)(span);
|
|
302
|
+
attributes[ATTRIBUTE_KEYS.SPAN_TYPE] = span.type;
|
|
303
|
+
attributes[ATTRIBUTE_KEYS.ORIGIN] = "auto.ai.mastra";
|
|
304
|
+
if (span.metadata) Object.entries(span.metadata).forEach(([key, value]) => {
|
|
305
|
+
if (value !== void 0 && value !== null && key !== "langfuse") attributes[`metadata.${key}`] = this.serializeValue(value);
|
|
306
|
+
});
|
|
307
|
+
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.TAGS, span.tags?.join(","));
|
|
308
|
+
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.GEN_AI_CONVERSATION_ID, span.metadata?.threadId);
|
|
309
|
+
this.addInputOutputAttributes(attributes, span);
|
|
310
|
+
if (span.type === _mastra_core_observability.SpanType.MODEL_GENERATION) this.addModelGenerationAttributes(attributes, span);
|
|
311
|
+
if (span.type === _mastra_core_observability.SpanType.TOOL_CALL || span.type === _mastra_core_observability.SpanType.PROVIDER_TOOL_CALL) this.addToolCallAttributes(attributes, span);
|
|
312
|
+
if (span.type === _mastra_core_observability.SpanType.AGENT_RUN) this.addAgentRunAttributes(attributes, span);
|
|
313
|
+
if (span.type === _mastra_core_observability.SpanType.WORKFLOW_RUN) {
|
|
314
|
+
const workflowAttr = span.attributes;
|
|
315
|
+
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.WORKFLOW_ID, this.getEntityName(span));
|
|
316
|
+
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.WORKFLOW_STATUS, workflowAttr.status);
|
|
317
|
+
}
|
|
318
|
+
if (span.type === _mastra_core_observability.SpanType.WORKFLOW_STEP) {
|
|
319
|
+
const stepAttr = span.attributes;
|
|
320
|
+
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.WORKFLOW_STEP_ID, this.getEntityName(span));
|
|
321
|
+
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.WORKFLOW_STEP_STATUS, stepAttr.status);
|
|
322
|
+
}
|
|
323
|
+
return attributes;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Adds Sentry-specific input/output attributes that complement GenAI semantic conventions.
|
|
327
|
+
* Adds 'input' and 'output' keys for Sentry UI compatibility.
|
|
328
|
+
*/
|
|
329
|
+
addInputOutputAttributes(attributes, span) {
|
|
330
|
+
if (span.input !== void 0) attributes[ATTRIBUTE_KEYS.INPUT] = this.serializeValue(span.input);
|
|
331
|
+
if (span.output !== void 0) {
|
|
332
|
+
attributes[ATTRIBUTE_KEYS.OUTPUT] = this.serializeValue(span.output);
|
|
333
|
+
if (span.type === _mastra_core_observability.SpanType.MODEL_GENERATION) {
|
|
334
|
+
const outputText = this.extractOutputText(span.output);
|
|
335
|
+
if (outputText) attributes[ATTRIBUTE_KEYS.GEN_AI_RESPONSE_TEXT] = outputText;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Adds Sentry-specific MODEL_GENERATION attributes that complement GenAI semantic conventions.
|
|
341
|
+
*/
|
|
342
|
+
addModelGenerationAttributes(attributes, span) {
|
|
343
|
+
const modelAttr = span.attributes;
|
|
344
|
+
if (modelAttr.streaming !== void 0) {
|
|
345
|
+
attributes[ATTRIBUTE_KEYS.GEN_AI_REQUEST_STREAM] = modelAttr.streaming;
|
|
346
|
+
attributes[ATTRIBUTE_KEYS.GEN_AI_RESPONSE_STREAMING] = modelAttr.streaming;
|
|
347
|
+
}
|
|
348
|
+
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.GEN_AI_COMPLETION_START_TIME, modelAttr.completionStartTime?.toISOString());
|
|
349
|
+
if (modelAttr.usage) {
|
|
350
|
+
const totalTokens = (modelAttr.usage.inputTokens || 0) + (modelAttr.usage.outputTokens || 0);
|
|
351
|
+
if (totalTokens > 0) attributes[ATTRIBUTE_KEYS.GEN_AI_USAGE_TOTAL_TOKENS] = totalTokens;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Adds Sentry-specific TOOL_CALL attributes that complement GenAI semantic conventions.
|
|
356
|
+
*/
|
|
357
|
+
addToolCallAttributes(attributes, span) {
|
|
358
|
+
const toolAttr = span.attributes;
|
|
359
|
+
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.TOOL_SUCCESS, toolAttr.success);
|
|
360
|
+
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.GEN_AI_TOOL_CALL_ID, span.metadata?.toolCallId);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Adds Sentry-specific AGENT_RUN attributes that complement GenAI semantic conventions.
|
|
364
|
+
*/
|
|
365
|
+
addAgentRunAttributes(attributes, span) {
|
|
366
|
+
const agentAttr = span.attributes;
|
|
367
|
+
const agentName = this.getEntityName(span);
|
|
368
|
+
if (agentName) attributes[ATTRIBUTE_KEYS.GEN_AI_PIPELINE_NAME] = agentName;
|
|
369
|
+
this.setAttributeIfDefined(attributes, ATTRIBUTE_KEYS.GEN_AI_AGENT_PROMPT, agentAttr.prompt);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Applies token usage from the MODEL_GENERATION span to the AGENT_RUN span attributes.
|
|
373
|
+
* Reads usage directly from the generation field.
|
|
374
|
+
* Called when AGENT_RUN spans end to set gen_ai.usage.* attributes.
|
|
375
|
+
*/
|
|
376
|
+
applyUsageFromGeneration(spanData) {
|
|
377
|
+
const usage = spanData.generation?.usage;
|
|
378
|
+
if (!usage) return;
|
|
379
|
+
const inputTokens = usage.inputTokens || 0;
|
|
380
|
+
const outputTokens = usage.outputTokens || 0;
|
|
381
|
+
if (inputTokens > 0) spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_USAGE_INPUT_TOKENS, inputTokens);
|
|
382
|
+
if (outputTokens > 0) spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_USAGE_OUTPUT_TOKENS, outputTokens);
|
|
383
|
+
const totalTokens = inputTokens + outputTokens;
|
|
384
|
+
if (totalTokens > 0) spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_USAGE_TOTAL_TOKENS, totalTokens);
|
|
385
|
+
const cacheReadTokens = usage.inputDetails?.cacheRead || 0;
|
|
386
|
+
const cacheWriteTokens = usage.inputDetails?.cacheWrite || 0;
|
|
387
|
+
const reasoningTokens = usage.outputDetails?.reasoning || 0;
|
|
388
|
+
if (cacheReadTokens > 0) spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_USAGE_CACHE_READ_TOKENS, cacheReadTokens);
|
|
389
|
+
if (cacheWriteTokens > 0) spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_USAGE_CACHE_WRITE_TOKENS, cacheWriteTokens);
|
|
390
|
+
if (reasoningTokens > 0) spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_USAGE_REASONING_TOKENS, reasoningTokens);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Sets gen_ai.response.model and gen_ai.response.text from the MODEL_GENERATION.
|
|
394
|
+
* Only applies to AGENT_RUN spans.
|
|
395
|
+
*/
|
|
396
|
+
setGenerationResponseAttributes(spanData) {
|
|
397
|
+
if (!spanData.generation) return;
|
|
398
|
+
if (spanData.generation.model) spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_RESPONSE_MODEL, spanData.generation.model);
|
|
399
|
+
if (spanData.generation.output) {
|
|
400
|
+
const outputText = this.extractOutputText(spanData.generation.output);
|
|
401
|
+
if (outputText) spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_RESPONSE_TEXT, outputText);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Tracks a TOOL_CALL span as a child of its parent MODEL_GENERATION span.
|
|
406
|
+
* This builds the tool_calls array for gen_ai.response.tool_calls attribute.
|
|
407
|
+
*/
|
|
408
|
+
trackToolCallForParent(span, parentId) {
|
|
409
|
+
const parentSpanData = this.spanMap.get(parentId);
|
|
410
|
+
if (!parentSpanData || parentSpanData.spanType !== _mastra_core_observability.SpanType.MODEL_GENERATION) return;
|
|
411
|
+
const toolAttr = span.attributes;
|
|
412
|
+
if (!parentSpanData.toolCalls) parentSpanData.toolCalls = [];
|
|
413
|
+
parentSpanData.toolCalls.push({
|
|
414
|
+
name: this.getEntityName(span),
|
|
415
|
+
id: span.metadata?.toolCallId,
|
|
416
|
+
type: toolAttr.toolType || "function"
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Applies the gen_ai.response.tool_calls attribute to MODEL_GENERATION spans.
|
|
421
|
+
* Called when MODEL_GENERATION spans end if they have child tool calls.
|
|
422
|
+
*/
|
|
423
|
+
applyToolCallsAttribute(spanData) {
|
|
424
|
+
if (!spanData.toolCalls || spanData.toolCalls.length === 0) return;
|
|
425
|
+
spanData.span.setAttribute(ATTRIBUTE_KEYS.GEN_AI_RESPONSE_TOOL_CALLS, JSON.stringify(spanData.toolCalls));
|
|
426
|
+
}
|
|
427
|
+
logMissingSpan(span, operation) {
|
|
428
|
+
this.logger.warn(`Sentry exporter: No Sentry span found for ${operation}`, {
|
|
429
|
+
traceId: span.traceId,
|
|
430
|
+
spanId: span.id,
|
|
431
|
+
spanName: span.name
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
getEntityName(span) {
|
|
435
|
+
return span.entityName || span.entityId || "unknown";
|
|
436
|
+
}
|
|
437
|
+
extractOutputText(output) {
|
|
438
|
+
if (!output) return void 0;
|
|
439
|
+
if (typeof output === "string") return output;
|
|
440
|
+
if (output.text && typeof output.text === "string") return output.text;
|
|
441
|
+
if (output.content && typeof output.content === "string") return output.content;
|
|
442
|
+
if (output.message?.content && typeof output.message.content === "string") return output.message.content;
|
|
443
|
+
}
|
|
444
|
+
serializeValue(value) {
|
|
445
|
+
if (value === null || value === void 0) return value;
|
|
446
|
+
if (typeof value === "object") try {
|
|
447
|
+
return JSON.stringify(value);
|
|
448
|
+
} catch {
|
|
449
|
+
return String(value);
|
|
450
|
+
}
|
|
451
|
+
return value;
|
|
452
|
+
}
|
|
453
|
+
setAttributeIfDefined(attributes, key, value) {
|
|
454
|
+
if (value !== void 0 && value !== null) attributes[key] = value;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Force flush any buffered spans without shutting down the exporter.
|
|
458
|
+
* This is useful in serverless environments where you need to ensure spans
|
|
459
|
+
* are exported before the runtime instance is terminated.
|
|
460
|
+
*/
|
|
461
|
+
async flush() {
|
|
462
|
+
if (!this.initialized) return;
|
|
463
|
+
try {
|
|
464
|
+
await _sentry_node.flush(2e3);
|
|
465
|
+
this.logger.debug("Sentry exporter: Flushed pending events");
|
|
466
|
+
} catch (error) {
|
|
467
|
+
this.logger.error("Sentry exporter: Error flushing events", { error });
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
async shutdown() {
|
|
471
|
+
for (const [spanId, spanData] of this.spanMap.entries()) try {
|
|
472
|
+
spanData.span.end();
|
|
473
|
+
} catch (error) {
|
|
474
|
+
this.logger.error("Sentry exporter: Error ending span during shutdown", {
|
|
475
|
+
spanId,
|
|
476
|
+
error
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
this.spanMap.clear();
|
|
480
|
+
this.skippedSpans.clear();
|
|
481
|
+
if (this.initialized) await _sentry_node.close(2e3);
|
|
482
|
+
await super.shutdown();
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
//#endregion
|
|
504
486
|
exports.SentryExporter = SentryExporter;
|
|
505
|
-
|
|
487
|
+
|
|
506
488
|
//# sourceMappingURL=index.cjs.map
|