-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix(sveltekit): Ensure sub requests are recorded as child spans of parent request #11130
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
11 commits
Select commit
Hold shift + click to select a range
74bdf4a
ref(sveltekit): Use `isSubRequest` flag to decide on server root span…
Lms24 99e9445
add unit test
Lms24 c0b8e3a
wip test
Lms24 2566eba
avoid nested continueTrace calls
Lms24 e9f04fa
more cleanup
Lms24 08ce71f
more more cleanup
Lms24 ce06869
add sveltekit-2 e2e test
Lms24 ab36435
fix e2e tests
Lms24 1e8fc5d
biome once again :(
Lms24 f51b8f2
sveltekit-2 proxy server
Lms24 8d38097
downgrade e2e test, remove length assertio
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
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
5 changes: 5 additions & 0 deletions
5
...ages/e2e-tests/test-applications/sveltekit-2/src/routes/server-load-fetch/+page.server.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,5 @@ | ||
export const load = async ({ fetch }) => { | ||
const res = await fetch('/api/users'); | ||
const data = await res.json(); | ||
return { data }; | ||
}; |
8 changes: 8 additions & 0 deletions
8
...ackages/e2e-tests/test-applications/sveltekit-2/src/routes/server-load-fetch/+page.svelte
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 @@ | ||
<script lang="ts"> | ||
export let data; | ||
</script> | ||
|
||
<main> | ||
<h1>Server Load Fetch</h1> | ||
<p>{JSON.stringify(data, null, 2)}</p> | ||
</main> |
35 changes: 35 additions & 0 deletions
35
dev-packages/e2e-tests/test-applications/sveltekit-2/test/performance.server.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, test } from '@playwright/test'; | ||
import { waitForTransaction } from '../event-proxy-server'; | ||
|
||
test('server pageload request span has nested request span for sub request', async ({ page }) => { | ||
const serverTxnEventPromise = waitForTransaction('sveltekit-2', txnEvent => { | ||
return txnEvent?.transaction === 'GET /server-load-fetch'; | ||
}); | ||
|
||
await page.goto('/server-load-fetch'); | ||
|
||
const serverTxnEvent = await serverTxnEventPromise; | ||
const spans = serverTxnEvent.spans; | ||
|
||
expect(serverTxnEvent).toMatchObject({ | ||
transaction: 'GET /server-load-fetch', | ||
tags: { runtime: 'node' }, | ||
transaction_info: { source: 'route' }, | ||
type: 'transaction', | ||
contexts: { | ||
trace: { | ||
op: 'http.server', | ||
origin: 'auto.http.sveltekit', | ||
}, | ||
}, | ||
}); | ||
|
||
expect(spans).toEqual( | ||
expect.arrayContaining([ | ||
// load span where the server load function initiates the sub request: | ||
expect.objectContaining({ op: 'function.sveltekit.server.load', description: '/server-load-fetch' }), | ||
// sub request span: | ||
expect.objectContaining({ op: 'http.server', description: 'GET /api/users' }), | ||
]), | ||
); | ||
}); |
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
5 changes: 5 additions & 0 deletions
5
...ckages/e2e-tests/test-applications/sveltekit/src/routes/server-load-fetch/+page.server.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,5 @@ | ||
export const load = async ({ fetch }) => { | ||
const res = await fetch('/api/users'); | ||
const data = await res.json(); | ||
return { data }; | ||
}; |
8 changes: 8 additions & 0 deletions
8
dev-packages/e2e-tests/test-applications/sveltekit/src/routes/server-load-fetch/+page.svelte
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 @@ | ||
<script lang="ts"> | ||
export let data; | ||
</script> | ||
|
||
<main> | ||
<h1>Server Load Fetch</h1> | ||
<p>{JSON.stringify(data, null, 2)}</p> | ||
</main> |
35 changes: 35 additions & 0 deletions
35
dev-packages/e2e-tests/test-applications/sveltekit/test/performance.server.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, test } from '@playwright/test'; | ||
import { waitForTransaction } from '../event-proxy-server'; | ||
|
||
test('server pageload request span has nested request span for sub request', async ({ page }) => { | ||
const serverTxnEventPromise = waitForTransaction('sveltekit', txnEvent => { | ||
return txnEvent?.transaction === 'GET /server-load-fetch'; | ||
}); | ||
|
||
await page.goto('/server-load-fetch'); | ||
|
||
const serverTxnEvent = await serverTxnEventPromise; | ||
const spans = serverTxnEvent.spans; | ||
|
||
expect(serverTxnEvent).toMatchObject({ | ||
transaction: 'GET /server-load-fetch', | ||
tags: { runtime: 'node' }, | ||
transaction_info: { source: 'route' }, | ||
type: 'transaction', | ||
contexts: { | ||
trace: { | ||
op: 'http.server', | ||
origin: 'auto.http.sveltekit', | ||
}, | ||
}, | ||
}); | ||
|
||
expect(spans).toEqual( | ||
expect.arrayContaining([ | ||
// load span where the server load function initiates the sub request: | ||
expect.objectContaining({ op: 'function.sveltekit.server.load', description: '/server-load-fetch' }), | ||
// sub request span: | ||
expect.objectContaining({ op: 'http.server', description: 'GET /api/users' }), | ||
]), | ||
); | ||
}); |
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 |
---|---|---|
|
@@ -149,15 +149,26 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { | |
}; | ||
|
||
const sentryRequestHandler: Handle = input => { | ||
// if there is an active span, we know that this handle call is nested and hence | ||
// we don't create a new execution context for it. | ||
// If we created one, nested server calls would create new root span instead | ||
// of adding a child span to the currently active span. | ||
if (getActiveSpan()) { | ||
// event.isSubRequest was added in SvelteKit 1.21.0 and we can use it to check | ||
// if we should create a new execution context or not. | ||
// In case of a same-origin `fetch` call within a server`load` function, | ||
// SvelteKit will actually just re-enter the `handle` function and set `isSubRequest` | ||
// to `true` so that no additional network call is made. | ||
// We want the `http.server` span of that nested call to be a child span of the | ||
// currently active span instead of a new root span to correctly reflect this | ||
// behavior. | ||
// As a fallback for Kit < 1.21.0, we check if there is an active span only if there's none, | ||
// we create a new execution context. | ||
const isSubRequest = typeof input.event.isSubRequest === 'boolean' ? input.event.isSubRequest : !!getActiveSpan(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yo this is surprisingly convenient 😂 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I was really happy when I found sveltejs/kit#10170 😅 |
||
|
||
if (isSubRequest) { | ||
return instrumentHandle(input, options); | ||
} | ||
|
||
return withIsolationScope(() => { | ||
return instrumentHandle(input, options); | ||
// We only call continueTrace in the initial top level request to avoid | ||
// creating a new root span for the sub request. | ||
return continueTrace(getTracePropagationData(input.event), () => instrumentHandle(input, options)); | ||
}); | ||
}; | ||
|
||
|
@@ -172,36 +183,32 @@ async function instrumentHandle( | |
return resolve(event); | ||
} | ||
|
||
const { sentryTrace, baggage } = getTracePropagationData(event); | ||
|
||
return continueTrace({ sentryTrace, baggage }, async () => { | ||
try { | ||
const resolveResult = await startSpan( | ||
{ | ||
op: 'http.server', | ||
attributes: { | ||
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit', | ||
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: event.route?.id ? 'route' : 'url', | ||
'http.method': event.request.method, | ||
}, | ||
name: `${event.request.method} ${event.route?.id || event.url.pathname}`, | ||
}, | ||
async (span?: Span) => { | ||
const res = await resolve(event, { | ||
transformPageChunk: addSentryCodeToPage(options), | ||
}); | ||
if (span) { | ||
setHttpStatus(span, res.status); | ||
} | ||
return res; | ||
try { | ||
const resolveResult = await startSpan( | ||
{ | ||
op: 'http.server', | ||
attributes: { | ||
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit', | ||
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: event.route?.id ? 'route' : 'url', | ||
'http.method': event.request.method, | ||
}, | ||
); | ||
return resolveResult; | ||
} catch (e: unknown) { | ||
sendErrorToSentry(e); | ||
throw e; | ||
} finally { | ||
await flushIfServerless(); | ||
} | ||
}); | ||
name: `${event.request.method} ${event.route?.id || event.url.pathname}`, | ||
}, | ||
async (span?: Span) => { | ||
const res = await resolve(event, { | ||
transformPageChunk: addSentryCodeToPage(options), | ||
}); | ||
if (span) { | ||
setHttpStatus(span, res.status); | ||
} | ||
return res; | ||
}, | ||
); | ||
return resolveResult; | ||
} catch (e: unknown) { | ||
sendErrorToSentry(e); | ||
throw e; | ||
} finally { | ||
await flushIfServerless(); | ||
} | ||
} |
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
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am worried about sveltekit 1 here because shouldn't there always be a span with the OTEL http instrumentation? Or was it that the http integration doesn't work in sveltekit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
http
integration doesn't seem to do anything in Kit :(But you're raising a good point. I'll check what
getActiveSpan
now returns going forward. i.e. if it returns something like aNonRecordingSpan
if there previously was no span. In that case, we need to rework the check a bit.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked for Kit < 1.21.0 and we still return
undefined
in this case. So the logic works correctly here. To make sure we cover this correctly in the future, I downgraded to kit 1.20.5 in our Kit 1.x E2E test version.