Skip to content

feat(core): Send gen_ai spans as v2 envelope items - #20342

Closed
andreiborza wants to merge 22 commits into
developfrom
ab/gen-ai-span-v2-poc
Closed

feat(core): Send gen_ai spans as v2 envelope items#20342
andreiborza wants to merge 22 commits into
developfrom
ab/gen-ai-span-v2-poc

Conversation

@andreiborza

@andreiborzaandreiborza commented Apr 16, 2026

Copy link
Copy Markdown
Member

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_ai spans out of the event , converts them to span v2 spans and attaches a span v2 container to the envelope.

@andreiborza
andreiborzaforce-pushed the ab/gen-ai-span-v2-poc branch from e02cd9b to d0cb9d0CompareApril 16, 2026 07:46
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: span.origin,
});

// Enrich from transaction event context (same pattern as captureSpan.ts applyCommonSpanAttributes)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's not do this, keep it simple for the time being!

@github-actions

github-actionsBot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize% ChangeChange
@sentry/browser26.34 kB+1.79%+462 B 🔺
@sentry/browser - with treeshaking flags24.8 kB+1.85%+449 B 🔺
@sentry/browser (incl. Tracing)44.26 kB+1.04%+453 B 🔺
@sentry/browser (incl. Tracing + Span Streaming)45.75 kB+0.54%+243 B 🔺
@sentry/browser (incl. Tracing, Profiling)49.24 kB+1.05%+507 B 🔺
@sentry/browser (incl. Tracing, Replay)83.45 kB+0.57%+468 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags72.96 kB+0.64%+463 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas)88.14 kB+0.54%+470 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback)100.43 kB+0.51%+502 B 🔺
@sentry/browser (incl. Feedback)43.15 kB+1.06%+449 B 🔺
@sentry/browser (incl. sendFeedback)31.01 kB+1.51%+460 B 🔺
@sentry/browser (incl. FeedbackAsync)36.01 kB+1.29%+458 B 🔺
@sentry/browser (incl. Metrics)27.41 kB+0.91%+245 B 🔺
@sentry/browser (incl. Logs)27.55 kB+0.97%+263 B 🔺
@sentry/browser (incl. Metrics & Logs)28.23 kB+0.88%+246 B 🔺
@sentry/react28.09 kB+1.69%+466 B 🔺
@sentry/react (incl. Tracing)46.53 kB+1.06%+488 B 🔺
@sentry/vue31.2 kB+1.59%+488 B 🔺
@sentry/vue (incl. Tracing)46.09 kB+1.02%+463 B 🔺
@sentry/svelte26.36 kB+1.8%+466 B 🔺
CDN Bundle28.81 kB+0.92%+262 B 🔺
CDN Bundle (incl. Tracing)45.16 kB+0.51%+226 B 🔺
CDN Bundle (incl. Logs, Metrics)30.18 kB+0.84%+251 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics)46.25 kB+0.49%+225 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics)69.12 kB+0.34%+228 B 🔺
CDN Bundle (incl. Tracing, Replay)82.17 kB+0.26%+213 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics)83.27 kB+0.3%+248 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback)87.69 kB+0.26%+225 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics)88.78 kB+0.27%+234 B 🔺
CDN Bundle - uncompressed84.26 kB+1.03%+855 B 🔺
CDN Bundle (incl. Tracing) - uncompressed135.16 kB+0.64%+859 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed88.4 kB+0.98%+855 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed138.57 kB+0.63%+859 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed211.98 kB+0.41%+855 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed252.6 kB+0.35%+859 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed256 kB+0.34%+859 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed265.52 kB+0.33%+859 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed268.9 kB+0.33%+859 B 🔺
@sentry/nextjs (client)49.04 kB+0.95%+458 B 🔺
@sentry/sveltekit (client)44.67 kB+1.03%+452 B 🔺
@sentry/node-core58.23 kB+0.38%+218 B 🔺
@sentry/node175.17 kB+0.17%+288 B 🔺
@sentry/node - without tracing98.25 kB+0.29%+284 B 🔺
@sentry/aws-serverless115.5 kB+0.28%+313 B 🔺

View base workflow run

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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' {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: we could make this slightly more size-efficient with an inlined ternary instead of the function. But feel free to disregard.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, inlined it. Thanks!

Comment on lines +50 to +69
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,
}
: {}),
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, removed. Thanks!

Comment on lines +43 to +47
// Fold op and origin into attributes
safeSetSpanJSONAttributes(streamedSpan, {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: span.op,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: span.origin,
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, removed. Thanks!

@andreiborza
andreiborzaforce-pushed the ab/gen-ai-span-v2-poc branch 2 times, most recently from b8c8b65 to d153251CompareApril 16, 2026 09:06
@andreiborza
andreiborzaforce-pushed the ab/gen-ai-span-v2-poc branch from d153251 to 69b5cf8CompareApril 16, 2026 09:54
…nsactions
to lessen the extraction performance impact
@andreiborza
andreiborza requested review from Lms24 and mydeaApril 16, 2026 10:39

for (const span of event.spans) {
if (span.op?.startsWith('gen_ai.')) {
genAiSpans.push(span);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: not quite sure, but maybe we can just use splice here instead of creating a new array?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I at least got rid of the extra .map

Comment threadpackages/core/src/tracing/spans/spanJsonToStreamedSpan.ts
@andreiborza
andreiborzaforce-pushed the ab/gen-ai-span-v2-poc branch 2 times, most recently from d2e8767 to 9e6b1bbCompareApril 16, 2026 14:36

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@andreiborza
andreiborzaforce-pushed the ab/gen-ai-span-v2-poc branch 2 times, most recently from ac09aa4 to a27a074CompareApril 18, 2026 02:54
@andreiborza
andreiborzaforce-pushed the ab/gen-ai-span-v2-poc branch 2 times, most recently from 08a995b to c5b22d8CompareApril 20, 2026 01:48
…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>
@andreiborza
andreiborzaforce-pushed the ab/gen-ai-span-v2-poc branch 3 times, most recently from 1939010 to e93d819CompareApril 20, 2026 05:22
andreiborzaand others added 3 commits April 20, 2026 14:29
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>
andreiborzaand others added 10 commits April 20, 2026 14:29
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>
@andreiborza
andreiborzaforce-pushed the ab/gen-ai-span-v2-poc branch from e93d819 to 76d835cCompareApril 20, 2026 05:39
@andreiborza
andreiborza marked this pull request as ready for review April 20, 2026 06:02
Comment threadpackages/core/src/tracing/spans/spanJsonToStreamedSpan.ts

@cursorcursorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread.size-limit.js
@nicohrubec

Copy link
Copy Markdown
Member

Already merged in #20785

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@andreiborza@nicohrubec@mydea@Lms24@alexander-alderman-webb