-
-
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(solidstart): Add sentry
onBeforeResponse
middleware to enable …
…distributed tracing (#13221) Works by adding the Sentry middlware to your `src/middleware.ts` file: ```typescript import { sentryBeforeResponseMiddleware } from '@sentry/solidstart/middleware'; import { createMiddleware } from '@solidjs/start/middleware'; export default createMiddleware({ onBeforeResponse: [ sentryBeforeResponseMiddleware(), // Add your other middleware handlers after `sentryBeforeResponseMiddleware` ], }); ``` And specifying `./src/middleware.ts` in `app.config.ts` Closes: #12551 Co-authored-by: Lukas Stracke <[email protected]>
- Loading branch information
1 parent
bedc385
commit 4ebac94
Showing
7 changed files
with
194 additions
and
5 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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { getTraceData } from '@sentry/core'; | ||
import { addNonEnumerableProperty } from '@sentry/utils'; | ||
import type { ResponseMiddleware } from '@solidjs/start/middleware'; | ||
import type { FetchEvent } from '@solidjs/start/server'; | ||
|
||
export type ResponseMiddlewareResponse = Parameters<ResponseMiddleware>[1] & { | ||
__sentry_wrapped__?: boolean; | ||
}; | ||
|
||
function addMetaTagToHead(html: string): string { | ||
const { 'sentry-trace': sentryTrace, baggage } = getTraceData(); | ||
|
||
if (!sentryTrace) { | ||
return html; | ||
} | ||
|
||
const metaTags = [`<meta name="sentry-trace" content="${sentryTrace}">`]; | ||
|
||
if (baggage) { | ||
metaTags.push(`<meta name="baggage" content="${baggage}">`); | ||
} | ||
|
||
const content = `<head>\n${metaTags.join('\n')}\n`; | ||
return html.replace('<head>', content); | ||
} | ||
|
||
/** | ||
* Returns an `onBeforeResponse` solid start middleware handler that adds tracing data as | ||
* <meta> tags to a page on pageload to enable distributed tracing. | ||
*/ | ||
export function sentryBeforeResponseMiddleware() { | ||
return async function onBeforeResponse(event: FetchEvent, response: ResponseMiddlewareResponse) { | ||
if (!response.body || response.__sentry_wrapped__) { | ||
return; | ||
} | ||
|
||
// Ensure we don't double-wrap, in case a user has added the middleware twice | ||
// e.g. once manually, once via the wizard | ||
addNonEnumerableProperty(response, '__sentry_wrapped__', true); | ||
|
||
const contentType = event.response.headers.get('content-type'); | ||
const isPageloadRequest = contentType && contentType.startsWith('text/html'); | ||
|
||
if (!isPageloadRequest) { | ||
return; | ||
} | ||
|
||
const body = response.body as NodeJS.ReadableStream; | ||
const decoder = new TextDecoder(); | ||
response.body = new ReadableStream({ | ||
start: async controller => { | ||
for await (const chunk of body) { | ||
const html = typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true }); | ||
const modifiedHtml = addMetaTagToHead(html); | ||
controller.enqueue(new TextEncoder().encode(modifiedHtml)); | ||
} | ||
controller.close(); | ||
}, | ||
}); | ||
}; | ||
} |
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,82 @@ | ||
import * as SentryCore from '@sentry/core'; | ||
import { beforeEach, describe, it, vi } from 'vitest'; | ||
import { sentryBeforeResponseMiddleware } from '../src/middleware'; | ||
import type { ResponseMiddlewareResponse } from '../src/middleware'; | ||
|
||
describe('middleware', () => { | ||
describe('sentryBeforeResponseMiddleware', () => { | ||
vi.spyOn(SentryCore, 'getTraceData').mockReturnValue({ | ||
'sentry-trace': '123', | ||
baggage: 'abc', | ||
}); | ||
|
||
const mockFetchEvent = { | ||
request: {}, | ||
locals: {}, | ||
response: { | ||
// mocks a pageload | ||
headers: new Headers([['content-type', 'text/html']]), | ||
}, | ||
nativeEvent: {}, | ||
}; | ||
|
||
let mockMiddlewareHTMLResponse: ResponseMiddlewareResponse; | ||
let mockMiddlewareHTMLNoHeadResponse: ResponseMiddlewareResponse; | ||
let mockMiddlewareJSONResponse: ResponseMiddlewareResponse; | ||
|
||
beforeEach(() => { | ||
// h3 doesn't pass a proper Response object to the middleware | ||
mockMiddlewareHTMLResponse = { | ||
body: new Response('<head><meta charset="utf-8"></head>').body, | ||
}; | ||
mockMiddlewareHTMLNoHeadResponse = { | ||
body: new Response('<body>Hello World</body>').body, | ||
}; | ||
mockMiddlewareJSONResponse = { | ||
body: new Response('{"prefecture": "Kagoshima"}').body, | ||
}; | ||
}); | ||
|
||
it('injects tracing meta tags into the response body', async () => { | ||
const onBeforeResponse = sentryBeforeResponseMiddleware(); | ||
onBeforeResponse(mockFetchEvent, mockMiddlewareHTMLResponse); | ||
|
||
// for testing convenience, we pass the body back into a proper response | ||
// mockMiddlewareHTMLResponse has been modified by our middleware | ||
const html = await new Response(mockMiddlewareHTMLResponse.body).text(); | ||
expect(html).toContain('<meta charset="utf-8">'); | ||
expect(html).toContain('<meta name="sentry-trace" content="123">'); | ||
expect(html).toContain('<meta name="baggage" content="abc">'); | ||
}); | ||
|
||
it('does not add meta tags if there is no head tag', async () => { | ||
const onBeforeResponse = sentryBeforeResponseMiddleware(); | ||
onBeforeResponse(mockFetchEvent, mockMiddlewareHTMLNoHeadResponse); | ||
|
||
const html = await new Response(mockMiddlewareHTMLNoHeadResponse.body).text(); | ||
expect(html).toEqual('<body>Hello World</body>'); | ||
}); | ||
|
||
it('does not add tracing meta tags twice into the same response', async () => { | ||
const onBeforeResponse1 = sentryBeforeResponseMiddleware(); | ||
onBeforeResponse1(mockFetchEvent, mockMiddlewareHTMLResponse); | ||
|
||
const onBeforeResponse2 = sentryBeforeResponseMiddleware(); | ||
onBeforeResponse2(mockFetchEvent, mockMiddlewareHTMLResponse); | ||
|
||
const html = await new Response(mockMiddlewareHTMLResponse.body).text(); | ||
expect(html.match(/<meta name="sentry-trace" content="123">/g)).toHaveLength(1); | ||
expect(html.match(/<meta name="baggage" content="abc">/g)).toHaveLength(1); | ||
}); | ||
|
||
it('does not modify a non-HTML response', async () => { | ||
const onBeforeResponse = sentryBeforeResponseMiddleware(); | ||
onBeforeResponse({ ...mockFetchEvent, response: { headers: new Headers() } }, mockMiddlewareJSONResponse); | ||
|
||
const json = await new Response(mockMiddlewareJSONResponse.body).json(); | ||
expect(json).toEqual({ | ||
prefecture: 'Kagoshima', | ||
}); | ||
}); | ||
}); | ||
}); |
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