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