-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(tanstackstart-react): Add file exclude to configure middleware auto-instrumentation #19007
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
base: develop
Are you sure you want to change the base?
Changes from all commits
b877f51
40a7ba4
3660c72
9a8026b
e985b3e
f7f4c22
092858f
ce8dbc7
330d7f8
b262eaa
14873c3
a910011
c122987
25e86bc
1b2c92f
b94322a
e7f8e23
df42f71
40ba4ec
9912468
fc0632a
478e1a2
07f4f30
d41a450
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,9 @@ | ||
| import { stringMatchesSomePattern } from '@sentry/core'; | ||
| import type { Plugin } from 'vite'; | ||
|
|
||
| type AutoInstrumentMiddlewareOptions = { | ||
| enabled?: boolean; | ||
| debug?: boolean; | ||
| exclude?: Array<string | RegExp>; | ||
| }; | ||
|
|
||
| type WrapResult = { | ||
|
|
@@ -87,25 +88,43 @@ function applyWrap( | |
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Checks if a file should be skipped from auto-instrumentation based on exclude patterns. | ||
| */ | ||
| export function shouldSkipFile(id: string, exclude: Array<string | RegExp> | undefined, debug: boolean): boolean { | ||
| // file doesn't match exclude patterns, don't skip | ||
| if (!exclude || exclude.length === 0 || !stringMatchesSomePattern(id, exclude)) { | ||
| return false; | ||
| } | ||
|
|
||
| // file matches exclude patterns, skip | ||
| if (debug) { | ||
| // eslint-disable-next-line no-console | ||
| console.log(`[Sentry] Skipping auto-instrumentation for excluded file: ${id}`); | ||
|
Member
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. l: I think this should be rather |
||
| } | ||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * A Vite plugin that automatically instruments TanStack Start middlewares: | ||
| * - `requestMiddleware` and `functionMiddleware` arrays in `createStart()` | ||
| * - `middleware` arrays in `createFileRoute()` route definitions | ||
| */ | ||
| export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin { | ||
| const { enabled = true, debug = false } = options; | ||
| const { debug = false, exclude } = options; | ||
|
|
||
| return { | ||
| name: 'sentry-tanstack-middleware-auto-instrument', | ||
| enforce: 'pre', | ||
|
|
||
| transform(code, id) { | ||
| if (!enabled) { | ||
| // Skip if not a TS/JS file | ||
| if (!/\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) { | ||
|
Member
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. l: This was here before - still, maybe it makes more sense to have a |
||
| return null; | ||
| } | ||
|
|
||
| // Skip if not a TS/JS file | ||
| if (!/\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) { | ||
| // Skip if file matches exclude patterns | ||
| if (shouldSkipFile(id, exclude, debug)) { | ||
| return null; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,17 +8,34 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc | |
| */ | ||
| export interface SentryTanstackStartOptions extends BuildTimeOptionsBase { | ||
| /** | ||
| * If this flag is `true`, the Sentry plugins will automatically instrument TanStack Start middlewares. | ||
| * Configure automatic middleware instrumentation. | ||
| * | ||
| * This wraps global middlewares (`requestMiddleware` and `functionMiddleware`) in `createStart()` with Sentry | ||
| * instrumentation to capture performance data. | ||
| * - Set to `false` to disable automatic middleware instrumentation entirely. | ||
| * - Set to `true` (default) to enable for all middleware files. | ||
| * - Set to an object with `exclude` to enable but exclude specific files. | ||
| * | ||
| * Set to `false` to disable automatic middleware instrumentation if you prefer to wrap middlewares manually | ||
| * using `wrapMiddlewaresWithSentry`. | ||
| * The `exclude` option takes an array of strings or regular expressions matched | ||
| * against the full file path. String patterns match as substrings. | ||
| * | ||
| * @default true | ||
| * | ||
| * @example | ||
| * // Disable completely | ||
| * sentryTanstackStart({ autoInstrumentMiddleware: false }) | ||
| * | ||
| * @example | ||
| * // Enable with exclusions | ||
| * sentryTanstackStart({ | ||
| * autoInstrumentMiddleware: { | ||
| * exclude: ['/routes/admin/', /\.test\.ts$/], | ||
| * }, | ||
| * }) | ||
| */ | ||
| autoInstrumentMiddleware?: boolean; | ||
| autoInstrumentMiddleware?: | ||
| | boolean | ||
| | { | ||
| exclude?: Array<string | RegExp>; | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -54,8 +71,17 @@ export function sentryTanstackStart(options: SentryTanstackStartOptions = {}): P | |
| const plugins: Plugin[] = [...makeAddSentryVitePlugin(options)]; | ||
|
|
||
| // middleware auto-instrumentation | ||
| if (options.autoInstrumentMiddleware !== false) { | ||
| plugins.push(makeAutoInstrumentMiddlewarePlugin({ enabled: true, debug: options.debug })); | ||
| const autoInstrumentConfig = options.autoInstrumentMiddleware; | ||
| const isDisabled = autoInstrumentConfig === false; | ||
| const excludePatterns = typeof autoInstrumentConfig === 'object' ? autoInstrumentConfig.exclude : undefined; | ||
|
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. Bug: The check Suggested FixUpdate the conditional check to explicitly handle the Prompt for AI AgentDid we get this right? 👍 / 👎 to inform future reviews. |
||
|
|
||
| if (!isDisabled) { | ||
| plugins.push( | ||
| makeAutoInstrumentMiddlewarePlugin({ | ||
| debug: options.debug, | ||
| exclude: excludePatterns, | ||
| }), | ||
| ); | ||
| } | ||
|
|
||
| // source maps | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ import { | |
| addSentryImport, | ||
| arrayToObjectShorthand, | ||
| makeAutoInstrumentMiddlewarePlugin, | ||
| shouldSkipFile, | ||
| wrapGlobalMiddleware, | ||
| wrapRouteMiddleware, | ||
| wrapServerFnMiddleware, | ||
|
|
@@ -36,13 +37,6 @@ export const Route = createFileRoute('/foo')({ | |
| expect(result).toBeNull(); | ||
| }); | ||
|
|
||
| it('does not instrument when enabled is false', () => { | ||
| const plugin = makeAutoInstrumentMiddlewarePlugin({ enabled: false }) as PluginWithTransform; | ||
| const result = plugin.transform(createStartFile, '/app/start.ts'); | ||
|
|
||
| expect(result).toBeNull(); | ||
| }); | ||
|
|
||
| it('does not instrument files without createStart or createFileRoute', () => { | ||
| const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform; | ||
| const code = "export const foo = 'bar';"; | ||
|
|
@@ -97,6 +91,14 @@ createStart(() => ({ requestMiddleware: [getMiddleware()] })); | |
|
|
||
| consoleWarnSpy.mockRestore(); | ||
| }); | ||
|
|
||
| it('does not instrument files matching exclude patterns', () => { | ||
| const plugin = makeAutoInstrumentMiddlewarePlugin({ | ||
| exclude: ['/routes/admin/'], | ||
| }) as PluginWithTransform; | ||
| const result = plugin.transform(createStartFile, '/app/routes/admin/start.ts'); | ||
| expect(result).toBeNull(); | ||
| }); | ||
|
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. Missing integration or E2E tests for feat PRLow Severity · Bugbot Rules This Additional Locations (1) |
||
| }); | ||
|
|
||
| describe('wrapGlobalMiddleware', () => { | ||
|
|
@@ -445,6 +447,37 @@ describe('addSentryImport', () => { | |
| }); | ||
| }); | ||
|
|
||
| describe('shouldSkipFile', () => { | ||
| it('returns false when exclude is undefined', () => { | ||
| expect(shouldSkipFile('/app/start.ts', undefined, false)).toBe(false); | ||
| }); | ||
|
|
||
| it('returns false when exclude is empty array', () => { | ||
| expect(shouldSkipFile('/app/start.ts', [], false)).toBe(false); | ||
| }); | ||
|
|
||
| it('returns false when file does not match any pattern', () => { | ||
| expect(shouldSkipFile('/app/start.ts', ['/admin/', /\.test\.ts$/], false)).toBe(false); | ||
| }); | ||
|
|
||
| it('returns true when file matches string pattern', () => { | ||
| expect(shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], false)).toBe(true); | ||
| }); | ||
|
|
||
| it('returns true when file matches regex pattern', () => { | ||
| expect(shouldSkipFile('/app/start.test.ts', [/\.test\.ts$/], false)).toBe(true); | ||
| }); | ||
|
|
||
| it('logs debug message when skipping file', () => { | ||
| const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); | ||
| shouldSkipFile('/app/routes/admin/start.ts', ['/admin/'], true); | ||
| expect(consoleLogSpy).toHaveBeenCalledWith( | ||
| expect.stringContaining('Skipping auto-instrumentation for excluded file'), | ||
| ); | ||
| consoleLogSpy.mockRestore(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('arrayToObjectShorthand', () => { | ||
| it('converts single identifier', () => { | ||
| expect(arrayToObjectShorthand('foo')).toBe('{ foo }'); | ||
|
|
||


Uh oh!
There was an error while loading. Please reload this page.
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.
This was an unnecessary option since we just don't add this plugin if it's not enabled, so I removed it