feat(core): Send gen_ai spans as v2 envelope items#20342
feat(core): Send gen_ai spans as v2 envelope items#20342andreiborza wants to merge 19 commits intodevelopfrom
Conversation
e02cd9b to
d0cb9d0
Compare
| [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: span.origin, | ||
| }); | ||
|
|
||
| // Enrich from transaction event context (same pattern as captureSpan.ts applyCommonSpanAttributes) |
There was a problem hiding this comment.
let's not do this, keep it simple for the time being!
size-limit report 📦
|
Lms24
left a comment
There was a problem hiding this comment.
Generally LGTM. Agree with Francesco that we should only iterate over spans if we know that >0 ai spans are in the event. Left some more comments.
Regarding bundle size: I think this is fair to keep in core, especially if we manage to cut down on the common attribute application.
| return streamedSpanJsonToSerializedSpan(streamedSpan); | ||
| } | ||
|
|
||
| function mapV1StatusToV2(status: string | undefined): 'ok' | 'error' { |
There was a problem hiding this comment.
l: we could make this slightly more size-efficient with an inlined ternary instead of the function. But feel free to disregard.
There was a problem hiding this comment.
Yep, inlined it. Thanks!
| const sdk = client.getSdkMetadata(); | ||
| const { release, environment, sendDefaultPii } = client.getOptions(); | ||
|
|
||
| safeSetSpanJSONAttributes(streamedSpan, { | ||
| [SEMANTIC_ATTRIBUTE_SENTRY_RELEASE]: transactionEvent.release || release, | ||
| [SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT]: transactionEvent.environment || environment, | ||
| [SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME]: transactionEvent.transaction, | ||
| [SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: transactionEvent.contexts?.trace?.span_id, | ||
| [SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: sdk?.sdk?.name, | ||
| [SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: sdk?.sdk?.version, | ||
| ...(sendDefaultPii | ||
| ? { | ||
| [SEMANTIC_ATTRIBUTE_USER_ID]: transactionEvent.user?.id, | ||
| [SEMANTIC_ATTRIBUTE_USER_EMAIL]: transactionEvent.user?.email, | ||
| [SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS]: transactionEvent.user?.ip_address, | ||
| [SEMANTIC_ATTRIBUTE_USER_USERNAME]: transactionEvent.user?.username, | ||
| } | ||
| : {}), | ||
| }); | ||
|
|
There was a problem hiding this comment.
m: I don't think we need any of the common span attribute application logic, correct? We just map whatever is on the gen_ai span to a v2 span. Should save a decent amount of bytes.
There was a problem hiding this comment.
Right, removed. Thanks!
| // Fold op and origin into attributes | ||
| safeSetSpanJSONAttributes(streamedSpan, { | ||
| [SEMANTIC_ATTRIBUTE_SENTRY_OP]: span.op, | ||
| [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: span.origin, | ||
| }); |
There was a problem hiding this comment.
l: do we need this? Shouldn't the op -> sentry.op backfill have happened already? logaf-l though as only minimal hit and we're already defensive.
There was a problem hiding this comment.
Nope, removed. Thanks!
b8c8b65 to
d153251
Compare
d153251 to
69b5cf8
Compare
…nsactions to lessen the extraction performance impact
|
|
||
| for (const span of event.spans) { | ||
| if (span.op?.startsWith('gen_ai.')) { | ||
| genAiSpans.push(span); |
There was a problem hiding this comment.
l: not quite sure, but maybe we can just use splice here instead of creating a new array?
There was a problem hiding this comment.
Splicing becomes awkward, you'd have to backward splice, or do some pre-collecting of indices iteration... not much gain there.
If we splice backwards, we'd need to reverse gen_ai spans array, I think the code complexity and minimal performance gain from that (still have to reverse) is not worth it.
If we can send the ai spans in reverse order, we'd save some time but that's bound to create some confusion.
There was a problem hiding this comment.
I at least got rid of the extra .map
d2e8767 to
9e6b1bb
Compare
ac09aa4 to
a27a074
Compare
08a995b to
c5b22d8
Compare
…ion compat The v2 span attribute serializer currently drops array values. Stringifying arrays in processEndedVercelAiSpan keeps attributes like gen_ai.response.finish_reasons intact when the span is serialized to the v2 format. Can be removed once span streaming supports arrays natively. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1939010 to
e93d819
Compare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
e93d819 to
76d835c
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 76d835c. Configure here.

This PR is a proof of concept to send
gen_ai.*spans as span v2 envelope items alongside the legacy transaction, so they're processed by Sentry's new span ingestion pipeline. This only applies in non-span streaming mode.It pulls all
gen_aispans out of the event , converts them to span v2 spans and attaches a span v2 container to the envelope.