-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): Adapt spans for client-side fetch to streaming responses (#…
- Loading branch information
Showing
13 changed files
with
349 additions
and
45 deletions.
There are no files selected for viewing
This file contains 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 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
47 changes: 47 additions & 0 deletions
47
dev-packages/e2e-tests/test-applications/react-router-6/server/app.js
This file contains 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,47 @@ | ||
const express = require('express'); | ||
|
||
const app = express(); | ||
const PORT = 8080; | ||
|
||
const wait = time => { | ||
return new Promise(resolve => { | ||
setTimeout(() => { | ||
resolve(); | ||
}, time); | ||
}); | ||
}; | ||
|
||
async function sseHandler(request, response, timeout = false) { | ||
response.headers = { | ||
'Content-Type': 'text/event-stream', | ||
Connection: 'keep-alive', | ||
'Cache-Control': 'no-cache', | ||
'Access-Control-Allow-Origin': '*', | ||
}; | ||
|
||
response.setHeader('Cache-Control', 'no-cache'); | ||
response.setHeader('Content-Type', 'text/event-stream'); | ||
response.setHeader('Access-Control-Allow-Origin', '*'); | ||
response.setHeader('Connection', 'keep-alive'); | ||
|
||
response.flushHeaders(); | ||
|
||
await wait(2000); | ||
|
||
for (let index = 0; index < 10; index++) { | ||
response.write(`data: ${new Date().toISOString()}\n\n`); | ||
if (timeout) { | ||
await wait(10000); | ||
} | ||
} | ||
|
||
response.end(); | ||
} | ||
|
||
app.get('/sse', (req, res) => sseHandler(req, res)); | ||
|
||
app.get('/sse-timeout', (req, res) => sseHandler(req, res, true)); | ||
|
||
app.listen(PORT, () => { | ||
console.log(`SSE service listening at http://localhost:${PORT}`); | ||
}); |
This file contains 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
49 changes: 49 additions & 0 deletions
49
dev-packages/e2e-tests/test-applications/react-router-6/src/pages/SSE.tsx
This file contains 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,49 @@ | ||
import * as Sentry from '@sentry/react'; | ||
// biome-ignore lint/nursery/noUnusedImports: Need React import for JSX | ||
import * as React from 'react'; | ||
|
||
const fetchSSE = async ({ timeout }: { timeout: boolean }) => { | ||
Sentry.startSpanManual({ name: 'sse stream using fetch' }, async span => { | ||
const res = await Sentry.startSpan({ name: 'sse fetch call' }, async () => { | ||
const endpoint = `http://localhost:8080/${timeout ? 'sse-timeout' : 'sse'}`; | ||
return await fetch(endpoint); | ||
}); | ||
|
||
const stream = res.body; | ||
const reader = stream?.getReader(); | ||
|
||
const readChunk = async () => { | ||
const readRes = await reader?.read(); | ||
if (readRes?.done) { | ||
return; | ||
} | ||
|
||
new TextDecoder().decode(readRes?.value); | ||
|
||
await readChunk(); | ||
}; | ||
|
||
try { | ||
await readChunk(); | ||
} catch (error) { | ||
console.error('Could not fetch sse', error); | ||
} | ||
|
||
span.end(); | ||
}); | ||
}; | ||
|
||
const SSE = () => { | ||
return ( | ||
<> | ||
<button id="fetch-button" onClick={() => fetchSSE({ timeout: false })}> | ||
Fetch SSE | ||
</button> | ||
<button id="fetch-timeout-button" onClick={() => fetchSSE({ timeout: true })}> | ||
Fetch timeout SSE | ||
</button> | ||
</> | ||
); | ||
}; | ||
|
||
export default SSE; |
69 changes: 69 additions & 0 deletions
69
dev-packages/e2e-tests/test-applications/react-router-6/tests/sse.test.ts
This file contains 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,69 @@ | ||
import { expect, test } from '@playwright/test'; | ||
import { waitForTransaction } from '@sentry-internal/test-utils'; | ||
import { SpanJSON } from '@sentry/types'; | ||
|
||
test('Waits for sse streaming when creating spans', async ({ page }) => { | ||
await page.goto('/sse'); | ||
|
||
const transactionPromise = waitForTransaction('react-router-6', async transactionEvent => { | ||
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; | ||
}); | ||
|
||
const fetchButton = page.locator('id=fetch-button'); | ||
await fetchButton.click(); | ||
|
||
const rootSpan = await transactionPromise; | ||
const sseFetchCall = rootSpan.spans?.filter(span => span.description === 'sse fetch call')[0] as SpanJSON; | ||
const httpGet = rootSpan.spans?.filter(span => span.description === 'GET http://localhost:8080/sse')[0] as SpanJSON; | ||
|
||
expect(sseFetchCall).toBeDefined(); | ||
expect(httpGet).toBeDefined(); | ||
|
||
expect(sseFetchCall?.timestamp).toBeDefined(); | ||
expect(sseFetchCall?.start_timestamp).toBeDefined(); | ||
expect(httpGet?.timestamp).toBeDefined(); | ||
expect(httpGet?.start_timestamp).toBeDefined(); | ||
|
||
// http headers get sent instantly from the server | ||
const resolveDuration = Math.round((sseFetchCall.timestamp as number) - sseFetchCall.start_timestamp); | ||
|
||
// body streams after 2s | ||
const resolveBodyDuration = Math.round((httpGet.timestamp as number) - httpGet.start_timestamp); | ||
|
||
expect(resolveDuration).toBe(0); | ||
expect(resolveBodyDuration).toBe(2); | ||
}); | ||
|
||
test('Aborts when stream takes longer than 5s', async ({ page }) => { | ||
await page.goto('/sse'); | ||
|
||
const transactionPromise = waitForTransaction('react-router-6', async transactionEvent => { | ||
return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; | ||
}); | ||
|
||
const fetchButton = page.locator('id=fetch-timeout-button'); | ||
await fetchButton.click(); | ||
|
||
const rootSpan = await transactionPromise; | ||
const sseFetchCall = rootSpan.spans?.filter(span => span.description === 'sse fetch call')[0] as SpanJSON; | ||
const httpGet = rootSpan.spans?.filter( | ||
span => span.description === 'GET http://localhost:8080/sse-timeout', | ||
)[0] as SpanJSON; | ||
|
||
expect(sseFetchCall).toBeDefined(); | ||
expect(httpGet).toBeDefined(); | ||
|
||
expect(sseFetchCall?.timestamp).toBeDefined(); | ||
expect(sseFetchCall?.start_timestamp).toBeDefined(); | ||
expect(httpGet?.timestamp).toBeDefined(); | ||
expect(httpGet?.start_timestamp).toBeDefined(); | ||
|
||
// http headers get sent instantly from the server | ||
const resolveDuration = Math.round((sseFetchCall.timestamp as number) - sseFetchCall.start_timestamp); | ||
|
||
// body streams after 10s but client should abort reading after 5s | ||
const resolveBodyDuration = Math.round((httpGet.timestamp as number) - httpGet.start_timestamp); | ||
|
||
expect(resolveDuration).toBe(0); | ||
expect(resolveBodyDuration).toBe(7); | ||
}); |
This file contains 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 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 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 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.