-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(browser): Add setActiveSpanInBrowser to set an active span in the browser
#17714
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
410ca15
feat(browser): Add `setSpanActive` to create an active root span in …
Lms24 ee0fa54
add unit tests
Lms24 f941afc
remove unused import
Lms24 c4883fa
rename to `setActiveSpanInBrowser`
Lms24 3d36ae0
fix build
Lms24 fe6bacb
fix tests
Lms24 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
8 changes: 8 additions & 0 deletions
8
dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/init.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import * as Sentry from '@sentry/browser'; | ||
|
|
||
| window.Sentry = Sentry; | ||
|
|
||
| Sentry.init({ | ||
| dsn: 'https://[email protected]/1337', | ||
| tracesSampleRate: 1, | ||
| }); |
14 changes: 14 additions & 0 deletions
14
dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/subject.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' }); | ||
| Sentry.setActiveSpanInBrowser(checkoutSpan); | ||
|
|
||
| Sentry.startSpan({ name: 'checkout-step-1' }, () => { | ||
| Sentry.startSpan({ name: 'checkout-step-1-1' }, () => { | ||
| // ... ` | ||
| }); | ||
| }); | ||
|
|
||
| Sentry.startSpan({ name: 'checkout-step-2' }, () => { | ||
| // ... ` | ||
| }); | ||
|
|
||
| checkoutSpan.end(); |
35 changes: 35 additions & 0 deletions
35
dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { expect } from '@playwright/test'; | ||
| import { sentryTest } from '../../../../utils/fixtures'; | ||
| import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; | ||
|
|
||
| sentryTest('sets an inactive span active and adds child spans to it', async ({ getLocalTestUrl, page }) => { | ||
| if (shouldSkipTracingTest()) { | ||
| sentryTest.skip(); | ||
| } | ||
|
|
||
| const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow'); | ||
|
|
||
| const url = await getLocalTestUrl({ testDir: __dirname }); | ||
| await page.goto(url); | ||
|
|
||
| const checkoutEvent = envelopeRequestParser(await req); | ||
| const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id; | ||
| expect(checkoutSpanId).toMatch(/[a-f0-9]{16}/); | ||
|
|
||
| expect(checkoutEvent.spans).toHaveLength(3); | ||
|
|
||
| const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1'); | ||
| const checkoutStep11 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1-1'); | ||
| const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2'); | ||
|
|
||
| expect(checkoutStep1).toBeDefined(); | ||
| expect(checkoutStep11).toBeDefined(); | ||
| expect(checkoutStep2).toBeDefined(); | ||
|
|
||
| expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId); | ||
| expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId); | ||
|
|
||
| // despite 1-1 being called within 1, it's still parented to the root span | ||
| // due to this being default behaviour in browser environments | ||
| expect(checkoutStep11?.parent_span_id).toBe(checkoutSpanId); | ||
| }); |
9 changes: 9 additions & 0 deletions
9
...es/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/init.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import * as Sentry from '@sentry/browser'; | ||
|
|
||
| window.Sentry = Sentry; | ||
|
|
||
| Sentry.init({ | ||
| dsn: 'https://[email protected]/1337', | ||
| tracesSampleRate: 1, | ||
| parentSpanIsAlwaysRootSpan: false, | ||
| }); |
22 changes: 22 additions & 0 deletions
22
...browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/subject.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' }); | ||
| Sentry.setActiveSpanInBrowser(checkoutSpan); | ||
|
|
||
| Sentry.startSpan({ name: 'checkout-step-1' }, () => {}); | ||
|
|
||
| const checkoutStep2 = Sentry.startInactiveSpan({ name: 'checkout-step-2' }); | ||
| Sentry.setActiveSpanInBrowser(checkoutStep2); | ||
|
|
||
| Sentry.startSpan({ name: 'checkout-step-2-1' }, () => { | ||
| // ... ` | ||
| }); | ||
| checkoutStep2.end(); | ||
|
|
||
| Sentry.startSpan({ name: 'checkout-step-3' }, () => {}); | ||
|
|
||
| checkoutSpan.end(); | ||
|
|
||
| Sentry.startSpan({ name: 'post-checkout' }, () => { | ||
| Sentry.startSpan({ name: 'post-checkout-1' }, () => { | ||
| // ... ` | ||
| }); | ||
| }); |
57 changes: 57 additions & 0 deletions
57
...es/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import { expect } from '@playwright/test'; | ||
| import { sentryTest } from '../../../../utils/fixtures'; | ||
| import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; | ||
|
|
||
| sentryTest( | ||
| 'nested calls to setActiveSpanInBrowser with parentSpanIsAlwaysRootSpan=false result in correct parenting', | ||
| async ({ getLocalTestUrl, page }) => { | ||
| if (shouldSkipTracingTest()) { | ||
| sentryTest.skip(); | ||
| } | ||
|
|
||
| const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow'); | ||
| const postCheckoutReq = waitForTransactionRequest(page, e => e.transaction === 'post-checkout'); | ||
|
|
||
| const url = await getLocalTestUrl({ testDir: __dirname }); | ||
| await page.goto(url); | ||
|
|
||
| const checkoutEvent = envelopeRequestParser(await req); | ||
| const postCheckoutEvent = envelopeRequestParser(await postCheckoutReq); | ||
|
|
||
| const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id; | ||
| const postCheckoutSpanId = postCheckoutEvent.contexts?.trace?.span_id; | ||
|
|
||
| expect(checkoutSpanId).toMatch(/[a-f0-9]{16}/); | ||
| expect(postCheckoutSpanId).toMatch(/[a-f0-9]{16}/); | ||
|
|
||
| expect(checkoutEvent.spans).toHaveLength(4); | ||
| expect(postCheckoutEvent.spans).toHaveLength(1); | ||
|
|
||
| const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1'); | ||
| const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2'); | ||
| const checkoutStep21 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2-1'); | ||
| const checkoutStep3 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-3'); | ||
|
|
||
| expect(checkoutStep1).toBeDefined(); | ||
| expect(checkoutStep2).toBeDefined(); | ||
| expect(checkoutStep21).toBeDefined(); | ||
| expect(checkoutStep3).toBeDefined(); | ||
|
|
||
| expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId); | ||
| expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId); | ||
|
|
||
| // with parentSpanIsAlwaysRootSpan=false, 2-1 is parented to 2 because | ||
| // 2 was the active span when 2-1 was started | ||
| expect(checkoutStep21?.parent_span_id).toBe(checkoutStep2?.span_id); | ||
|
|
||
| // since the parent of three is `checkoutSpan`, we correctly reset | ||
| // the active span to `checkoutSpan` after 2 ended | ||
| expect(checkoutStep3?.parent_span_id).toBe(checkoutSpanId); | ||
|
|
||
| // post-checkout trace is started as a new trace because ending checkoutSpan removes the active | ||
| // span on the scope | ||
| const postCheckoutStep1 = postCheckoutEvent.spans?.find(s => s.description === 'post-checkout-1'); | ||
| expect(postCheckoutStep1).toBeDefined(); | ||
| expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId); | ||
| }, | ||
| ); |
8 changes: 8 additions & 0 deletions
8
dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/init.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import * as Sentry from '@sentry/browser'; | ||
|
|
||
| window.Sentry = Sentry; | ||
|
|
||
| Sentry.init({ | ||
| dsn: 'https://[email protected]/1337', | ||
| tracesSampleRate: 1, | ||
| }); |
22 changes: 22 additions & 0 deletions
22
dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/subject.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' }); | ||
| Sentry.setActiveSpanInBrowser(checkoutSpan); | ||
|
|
||
| Sentry.startSpan({ name: 'checkout-step-1' }, () => {}); | ||
|
|
||
| const checkoutStep2 = Sentry.startInactiveSpan({ name: 'checkout-step-2' }); | ||
| Sentry.setActiveSpanInBrowser(checkoutStep2); | ||
|
|
||
| Sentry.startSpan({ name: 'checkout-step-2-1' }, () => { | ||
| // ... ` | ||
| }); | ||
| checkoutStep2.end(); | ||
|
|
||
| Sentry.startSpan({ name: 'checkout-step-3' }, () => {}); | ||
|
|
||
| checkoutSpan.end(); | ||
|
|
||
| Sentry.startSpan({ name: 'post-checkout' }, () => { | ||
| Sentry.startSpan({ name: 'post-checkout-1' }, () => { | ||
| // ... ` | ||
| }); | ||
| }); |
52 changes: 52 additions & 0 deletions
52
dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import { expect } from '@playwright/test'; | ||
| import { sentryTest } from '../../../../utils/fixtures'; | ||
| import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; | ||
|
|
||
| sentryTest( | ||
| 'nested calls to setActiveSpanInBrowser still parent to root span by default', | ||
| async ({ getLocalTestUrl, page }) => { | ||
| if (shouldSkipTracingTest()) { | ||
| sentryTest.skip(); | ||
| } | ||
|
|
||
| const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow'); | ||
| const postCheckoutReq = waitForTransactionRequest(page, e => e.transaction === 'post-checkout'); | ||
|
|
||
| const url = await getLocalTestUrl({ testDir: __dirname }); | ||
| await page.goto(url); | ||
|
|
||
| const checkoutEvent = envelopeRequestParser(await req); | ||
| const postCheckoutEvent = envelopeRequestParser(await postCheckoutReq); | ||
|
|
||
| const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id; | ||
| const postCheckoutSpanId = postCheckoutEvent.contexts?.trace?.span_id; | ||
|
|
||
| expect(checkoutSpanId).toMatch(/[a-f0-9]{16}/); | ||
| expect(postCheckoutSpanId).toMatch(/[a-f0-9]{16}/); | ||
|
|
||
| expect(checkoutEvent.spans).toHaveLength(4); | ||
| expect(postCheckoutEvent.spans).toHaveLength(1); | ||
|
|
||
| const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1'); | ||
| const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2'); | ||
| const checkoutStep21 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2-1'); | ||
| const checkoutStep3 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-3'); | ||
|
|
||
| expect(checkoutStep1).toBeDefined(); | ||
| expect(checkoutStep2).toBeDefined(); | ||
| expect(checkoutStep21).toBeDefined(); | ||
| expect(checkoutStep3).toBeDefined(); | ||
|
|
||
| expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId); | ||
| expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId); | ||
| expect(checkoutStep3?.parent_span_id).toBe(checkoutSpanId); | ||
|
|
||
| // despite 2-1 being called within 2 AND setting 2 as active span, it's still parented to the | ||
| // root span due to this being default behaviour in browser environments | ||
| expect(checkoutStep21?.parent_span_id).toBe(checkoutSpanId); | ||
|
|
||
| const postCheckoutStep1 = postCheckoutEvent.spans?.find(s => s.description === 'post-checkout-1'); | ||
| expect(postCheckoutStep1).toBeDefined(); | ||
| expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId); | ||
| }, | ||
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import type { Span } from '@sentry/core'; | ||
| import { _INTERNAL_setSpanForScope, getActiveSpan, getCurrentScope } from '@sentry/core'; | ||
|
|
||
| /** | ||
| * Sets an inactive span active on the current scope. | ||
| * | ||
| * This is useful in browser applications, if you want to create a span that cannot be finished | ||
| * within its callback. Any spans started while the given span is active, will be children of the span. | ||
| * | ||
| * If there already was an active span on the scope prior to calling this function, it is replaced | ||
| * with the given span and restored after the span ended. Otherwise, the span will simply be | ||
| * removed, resulting in no active span on the scope. | ||
| * | ||
| * IMPORTANT: This function can ONLY be used in the browser! Calling this function in a server | ||
| * environment (for example in a server-side rendered component) will result in undefined behaviour | ||
| * and is not supported. | ||
| * You MUST call `span.end()` manually, otherwise the span will never be finished. | ||
| * | ||
| * @example | ||
| * ```js | ||
| * let checkoutSpan; | ||
| * | ||
| * on('checkoutStarted', () => { | ||
| * checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' }); | ||
| * Sentry.setActiveSpanInBrowser(checkoutSpan); | ||
| * }) | ||
| * | ||
| * // during this time, any spans started will be children of `checkoutSpan`: | ||
| * Sentry.startSpan({ name: 'checkout-step-1' }, () => { | ||
| * // ... ` | ||
| * }) | ||
| * | ||
| * on('checkoutCompleted', () => { | ||
| * checkoutSpan?.end(); | ||
| * }) | ||
| * ``` | ||
| * | ||
| * @param span - the span to set active | ||
| */ | ||
| export function setActiveSpanInBrowser(span: Span): void { | ||
| const maybePreviousActiveSpan = getActiveSpan(); | ||
|
|
||
| // If the span is already active, there's no need to double-patch or set it again. | ||
| // This also guards against users (for whatever reason) calling setActiveSpanInBrowser on SDK-started | ||
| // idle spans like pageload or navigation spans. These will already be handled correctly by the SDK. | ||
| // For nested situations, we have to double-patch to ensure we restore the correct previous span (see tests) | ||
| if (maybePreviousActiveSpan === span) { | ||
| return; | ||
| } | ||
|
|
||
| const scope = getCurrentScope(); | ||
|
|
||
| // Putting a small patch onto the span.end method to ensure we | ||
| // remove the span from the scope when it ends. | ||
| // eslint-disable-next-line @typescript-eslint/unbound-method | ||
| span.end = new Proxy(span.end, { | ||
| apply(target, thisArg, args: Parameters<Span['end']>) { | ||
| _INTERNAL_setSpanForScope(scope, maybePreviousActiveSpan); | ||
| return Reflect.apply(target, thisArg, args); | ||
| }, | ||
| }); | ||
|
|
||
| _INTERNAL_setSpanForScope(scope, span); | ||
| } | ||
Lms24 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.