Skip to content

Conversation

@BYK
Copy link
Member

@BYK BYK commented Nov 13, 2025

Implements full Spotlight spec with support for multiple framework-specific
environment variable prefixes. Adds defensive environment variable access
for both process.env and import.meta.env to support various bundlers.

Supported environment variables (in priority order):

  • PUBLIC_SENTRY_SPOTLIGHT (SvelteKit, Astro, Qwik)
  • NEXT_PUBLIC_SENTRY_SPOTLIGHT (Next.js)
  • VITE_SENTRY_SPOTLIGHT (Vite)
  • NUXT_PUBLIC_SENTRY_SPOTLIGHT (Nuxt)
  • REACT_APP_SENTRY_SPOTLIGHT (Create React App)
  • VUE_APP_SENTRY_SPOTLIGHT (Vue CLI)
  • GATSBY_SENTRY_SPOTLIGHT (Gatsby)
  • SENTRY_SPOTLIGHT (base/official)

SENTRY_SPOTLIGHT is last as in environments like Docker Compose, we actually make the front-end env variable different than the base SENTRY_SPOTLIGHT one -- the backends need to reach docker.host.internal whereas front-ends always need localhost as we assume the browser runs on the same host with Spotlight.

Refactors envToBool utility from node-core to core package for shared usage.
Adds resolveSpotlightOptions utility to ensure consistent precedence rules
across Browser and Node SDKs.

Includes comprehensive test coverage for all new utilities and integration
tests for environment variable precedence behavior.

Closes #18404

@github-actions
Copy link
Contributor

github-actions bot commented Nov 13, 2025

size-limit report 📦

Path Size % Change Change
@sentry/browser 24.81 kB - -
@sentry/browser - with treeshaking flags 23.3 kB - -
@sentry/browser (incl. Tracing) 41.55 kB - -
@sentry/browser (incl. Tracing, Profiling) 46.14 kB - -
@sentry/browser (incl. Tracing, Replay) 79.97 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 69.7 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 84.64 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 96.89 kB - -
@sentry/browser (incl. Feedback) 41.52 kB - -
@sentry/browser (incl. sendFeedback) 29.49 kB - -
@sentry/browser (incl. FeedbackAsync) 34.48 kB - -
@sentry/react 26.52 kB - -
@sentry/react (incl. Tracing) 43.75 kB - -
@sentry/vue 29.27 kB - -
@sentry/vue (incl. Tracing) 43.36 kB - -
@sentry/svelte 24.82 kB - -
CDN Bundle 27.28 kB +0.17% +45 B 🔺
CDN Bundle (incl. Tracing) 42.28 kB +0.12% +50 B 🔺
CDN Bundle (incl. Tracing, Replay) 78.81 kB +0.07% +53 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 84.25 kB +0.05% +41 B 🔺
CDN Bundle - uncompressed 80.12 kB +0.11% +83 B 🔺
CDN Bundle (incl. Tracing) - uncompressed 125.47 kB +0.07% +83 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed 241.5 kB +0.04% +83 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 254.27 kB +0.04% +83 B 🔺
@sentry/nextjs (client) 46.58 kB +1.32% +606 B 🔺
@sentry/sveltekit (client) 41.92 kB - -
@sentry/node-core 51.52 kB +0.05% +24 B 🔺
@sentry/node 160.33 kB +0.02% +23 B 🔺
@sentry/node - without tracing 92.92 kB +0.01% +9 B 🔺
@sentry/aws-serverless 108.46 kB +0.03% +27 B 🔺

View base workflow run

@github-actions
Copy link
Contributor

github-actions bot commented Nov 13, 2025

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 8,856 - 11,232 -21%
GET With Sentry 1,720 19% 2,005 -14%
GET With Sentry (error only) 6,042 68% 7,743 -22%
POST Baseline 1,182 - 1,134 +4%
POST With Sentry 580 49% 596 -3%
POST With Sentry (error only) 1,037 88% 1,036 +0%
MYSQL Baseline 3,305 - 3,992 -17%
MYSQL With Sentry 443 13% 520 -15%
MYSQL With Sentry (error only) 2,705 82% 3,320 -19%

View base workflow run

…ration

- Add support for multiple framework/bundler-specific environment variables with proper precedence
  - SENTRY_SPOTLIGHT (highest priority - base name, supported natively by many bundlers)
  - PUBLIC_SENTRY_SPOTLIGHT (SvelteKit, Astro, Qwik)
  - NEXT_PUBLIC_SENTRY_SPOTLIGHT (Next.js)
  - VITE_SENTRY_SPOTLIGHT (Vite)
  - NUXT_PUBLIC_SENTRY_SPOTLIGHT (Nuxt.js)
  - REACT_APP_SENTRY_SPOTLIGHT (Create React App)
  - VUE_APP_SENTRY_SPOTLIGHT (Vue CLI)
  - GATSBY_SENTRY_SPOTLIGHT (Gatsby)
- Add defensive environment variable access via process.env (transformed by all major bundlers)
- Move envToBool utility from node-core to core for shared usage
- Add resolveSpotlightOptions utility for consistent precedence rules
- Update node-core and aws-serverless to use shared utilities
- Add comprehensive tests for all new utilities and SDK integration

Note: import.meta.env is intentionally not checked because bundlers only replace static
references (e.g., import.meta.env.VITE_VAR) at build time, not dynamic access. All major
bundlers transform process.env references, making it the universal solution.
@BYK BYK force-pushed the feat/spotlight-environment-variable-support branch from 92ffd33 to 6cb5513 Compare November 13, 2025 22:51
@BYK BYK force-pushed the feat/spotlight-environment-variable-support branch from 4f6dcd0 to fda3d61 Compare November 14, 2025 11:31
@BYK BYK marked this pull request as ready for review November 14, 2025 11:32
@BYK BYK requested review from Lms24, andreiborza and timfish November 14, 2025 11:32
Before submitting a pull request, please take a look at our

[Contributing](https://github.com/getsentry/sentry-javascript/blob/master/CONTRIBUTING.md)
guidelines and verify:

- [x] If you've added code that should be tested, please add tests.
- [x] Ensure your code lints and the test suite passes (`yarn lint`) &
(`yarn test`).

Fixes a test failure where `getSpotlightConfig` was returning empty or
whitespace-only strings for `SENTRY_SPOTLIGHT` environment variables,
instead of `undefined`. This change explicitly filters out such values,
aligning the function's behavior with test expectations and preventing
invalid Spotlight configurations.

---
<a
href="https://cursor.com/background-agent?bcId=bc-88bd0365-3796-47b2-bb84-29c93d0430e8"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/open-in-cursor-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/open-in-cursor-light.svg"><img alt="Open in
Cursor"
src="https://cursor.com/open-in-cursor.svg"></picture></a>&nbsp;<a
href="https://cursor.com/agents?id=bc-88bd0365-3796-47b2-bb84-29c93d0430e8"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/open-in-web-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/open-in-web-light.svg"><img alt="Open in Web"
src="https://cursor.com/open-in-web.svg"></picture></a>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
@BYK BYK requested a review from a team as a code owner November 14, 2025 16:59
Copy link
Member

@Lms24 Lms24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass review. I still want to figure out why size bot reports a slight increase for the CDN bundles.

Comment on lines 3 to 9
* Checks process.env which is transformed by most bundlers (Webpack, Vite, Rollup, Rspack, Parcel, etc.)
* at build time.
*
* Note: We don't check import.meta.env because:
* 1. Bundlers only replace static references like `import.meta.env.VITE_VAR`, not dynamic access
* 2. Dynamic access causes syntax errors in unsupported environments
* 3. Most bundlers transform process.env references anyway
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: My impression while doing some research for #18050 (comment) was what very few bundlers inject process.env into browser bundles. For example, this code will not work in Angular.
Vite instructs checking on import.meta.env, so not sure if it double-writes to process.env (my gut feeling is no).

Did you test this in the respective frameworks? Maybe I'm also misinformed and this works just fine 😅 Ideally we can add an e2e test for at least NextJS and some Vite-based framework app to be sure.

Comment on lines 62 to 64

// No Spotlight configuration found in environment
return undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: neat size trick: You can let the function return void and simply omit the return undefined here. Saves a couple of bytes and JS returns undefined anyway. 😅 (but feel free to ignore since this is shaken out for prod builds anyway)

Before submitting a pull request, please take a look at our

[Contributing](https://github.com/getsentry/sentry-javascript/blob/master/CONTRIBUTING.md)
guidelines and verify:

- [x] If you've added code that should be tested, please add tests.
- [x] Ensure your code lints and the test suite passes (`yarn lint`) &
(`yarn test`).

---

This PR addresses critical feedback regarding environment variable
detection, particularly for Vite-based frameworks.

**Key Changes:**

* **`packages/browser/src/utils/env.ts`**: The `getEnvValue` function
now checks both `process.env` (for Webpack, Next.js, CRA) and
`import.meta.env` (for Vite, Astro, SvelteKit). This ensures that
environment variables (like those for Spotlight) are correctly detected
across a wider range of bundlers and frameworks, fixing a significant
compatibility issue.
* **`packages/browser/test/utils/env.test.ts`**: Updated unit tests to
focus on `process.env` scenarios. Added a note explaining that
`import.meta.env` cannot be unit tested due to its read-only,
compile-time nature and is covered by e2e tests.
* **`packages/browser/src/utils/spotlightConfig.ts`**: Added a comment
to clarify the explicit `return undefined` for readability, noting its
optimization in production builds.

---
<a
href="https://cursor.com/background-agent?bcId=bc-d6001dc9-59fc-42eb-8354-a573e3ae71a9"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/open-in-cursor-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/open-in-cursor-light.svg"><img alt="Open in
Cursor"
src="https://cursor.com/open-in-cursor.svg"></picture></a>&nbsp;<a
href="https://cursor.com/agents?id=bc-d6001dc9-59fc-42eb-8354-a573e3ae71a9"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/open-in-web-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/open-in-web-light.svg"><img alt="Open in Web"
src="https://cursor.com/open-in-web.svg"></picture></a>

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Copy link
Collaborator

@timfish timfish left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good but needs some e2e tests of some kind otherwise we could inadvertently break this in the future.

It should be quite easy to test Vite by adding some additional tests to dev-packages/bundler-tests with the specific variables set and then check they make it into the bundle.

Then as @Lms24 says, a Nextjs test would probably be a good idea too!

@BYK BYK requested a review from a team as a code owner November 20, 2025 14:08
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Breaking change: `envToBool` export removed from `@sentry/node-core` (Bugbot Rules)

The envToBool function was previously exported from @sentry/node-core but has been removed in this change (now exported from @sentry/core instead). This is a breaking change for any external consumers who were importing envToBool directly from @sentry/node-core. The function is still available from @sentry/core, but existing code using import { envToBool } from '@sentry/node-core' will break. Flagging this per the review rules about "Removal of publicly exported functions, classes, or types."

packages/node-core/src/index.ts#L44-L45

export { ensureIsWrapped } from './utils/ensureIsWrapped';
export { createMissingInstrumentationContext } from './utils/createMissingInstrumentationContext';

Fix in Cursor Fix in Web


BYK added 2 commits December 12, 2025 10:30
The timestamp filtering used process.hrtime() which returns time relative
to an arbitrary process-local reference point. Since the event proxy
server and Playwright test runner are separate Node.js processes, their
hrtime values were not comparable.

This caused waitForTransaction() to sometimes match transactions from
previous tests (stale events leaking from the buffer), leading to flaky
test failures like the react-router-7-lazy-routes duration assertion.

Fix: Use Date.now() (epoch milliseconds) plus a sub-millisecond component
from performance.now() to get high-resolution timestamps that are
comparable across processes.
The previous fix used performance.now() for sub-millisecond precision,
but this is also process-local and can cause incorrect comparisons.

If an event is buffered and a listener starts in the same millisecond
but with different performance.now() fractional parts, the stale event
can incorrectly pass the timestamp filter.

Fix: Use only Date.now() (milliseconds since epoch). This is the only
reliable cross-process timestamp. Millisecond precision is sufficient
since tests have gaps of 100ms+ between transactions.

Also renamed getNanosecondTimestamp to getTimestamp for accuracy.
BYK added 6 commits December 12, 2025 11:24
Change from >= to > when filtering buffered events. This prevents stale
events from previous tests leaking through when they were buffered at
the same millisecond as the new listener started.

New events arriving after the listener is registered go through the
callback directly (not the buffer), so they're unaffected by this
filter.
The timestamp filter only applied to BUFFERED events. Live events
arriving after a listener registered were sent to ALL listeners
without any timestamp check.

This caused stale events from previous tests to leak through if
the event was still in-flight when the next test started listening.

Fix: Track each listener's registration timestamp and filter live
events the same way as buffered events - only send events that
occurred AFTER the listener was registered.
…t time

The listener was using the client's timestamp from the query param, but
due to network latency, events could be buffered between when the client
generated its timestamp and when the HTTP request reached the server.

Timeline of the bug:
1. Client: T1 = 1000ms (generates timestamp)
2. Client: Sends HTTP request with ?timestamp=1000
3. [NETWORK DELAY]
4. Previous test's event arrives at proxy at T2 = 1002ms (buffered!)
5. Server receives HTTP request at T3 = 1003ms
7. Buffer check: 1002 > 1000 = TRUE → stale event passes!

Fix: Use the server's current time when the listener is actually
registered. This ensures we only receive events that arrived AFTER
the listener was registered on the server.
Live events arriving at the same millisecond as listener registration
were being filtered out, causing tests to timeout waiting for events
that never arrived.

- Buffered events: Use > (strict) - they might be from before registration
- Live events: Use >= - they are guaranteed to be new, not stale

This fixes the test timeouts while still preventing stale buffered
events from leaking through.
Instead of just filtering old events, actually remove them from the buffer.
This prevents stale events from ever being matched by future listeners,
even if timestamps are somehow compared incorrectly.

Events older than the listener registration time are definitely stale
and will never be needed by any listener.
- Add @spotlightjs/spotlight dependency to test-utils
- Create spotlight.ts with startSpotlight() and event waiting helpers
- Add Spotlight mode to getPlaywrightConfig() via useSpotlight option
- Migrate react-router-7-lazy-routes test app as proof of concept
  - Use DSN workaround (http://spotlight@localhost:8969/0)
  - Remove custom event proxy in favor of Spotlight
  - Update tests to use waitForSpotlightTransaction

This migration eliminates the custom event proxy server which had
timing issues causing flaky tests. Spotlight provides a more robust
solution for capturing and streaming Sentry events during E2E tests.
BYK added 6 commits December 12, 2025 13:48
The buffer pruning condition (<=) was inconsistent with the live event
sending condition (>=). This meant buffered events at exactly the same
millisecond as listener registration would be incorrectly pruned, even
though live events at the same timestamp would be sent.

With millisecond timestamps, same-timestamp collisions are more likely,
which could cause intermittent test failures. Changed to strict < to
match the >= behavior for live events.
…y-server.ts

- Add null check for portMatch[1] in spotlight.ts
- Add null check for eventBuffer element in generator
- Refactor buffer pruning loop to avoid 'possibly undefined' error
…cies

Installing Spotlight in individual test apps causes version conflicts
because Spotlight's transitive dependencies (e.g. @sentry/electron)
require specific @sentry/* versions that conflict with the freshly
built packages used by E2E tests.

Moving to root devDependencies avoids this conflict - the Spotlight CLI
is available globally without polluting test app dependency trees.
Reverting all timestamp/buffer filtering changes as they were breaking
other tests. The original implementation will be kept until the
Spotlight migration is complete.
- Detects the start script from package.json (`dev`, `develop`, `serve`, `start`)
- Starts the Spotlight sidecar on the specified port (or dynamic with `-p 0`)
- Streams events to stdout in JSON format (with `-f json`)
- Sets `SENTRY_SPOTLIGHT` env var for the child process
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Internal planning file accidentally committed to repository

The .cursor/plans/ directory contains internal planning documentation (a Cursor AI migration plan) that was accidentally committed to the repository. This file includes implementation todos and internal planning notes that likely shouldn't be part of the codebase.

Fix in Cursor Fix in Web

BYK added 3 commits December 12, 2025 14:35
The Spotlight approach had a fundamental architecture issue:
- Playwright's webServer spawns Spotlight as a separate process
- Tests run in the Playwright test runner process
- The event buffer in spotlight.ts only gets populated if startSpotlight()
  is called in the same process as the tests
- Since they're different processes, the buffer is always empty

Keeping spotlight.ts for future use but marking exports as experimental.
The test app is reverted to use the original event proxy approach.
Instead of using Playwright's webServer to spawn Spotlight (which runs
in a separate process and can't share the event buffer), tests now call
startSpotlight() directly in beforeAll hooks.

This way:
- Test process spawns Spotlight via `spotlight run -f json`
- Spotlight auto-runs the app and streams events to stdout
- Test process parses stdout and populates the event buffer
- waitForSpotlightTransaction() works because it's in the same process

Changes:
- playwright.config.mjs: Empty webServer (Spotlight handles app startup)
- src/index.tsx: Use Spotlight DSN workaround instead of tunnel
- tests/*.ts: Add beforeAll/afterAll for Spotlight lifecycle
- Deleted start-event-proxy.mjs (no longer needed)
- Detects the start script from package.json (`dev`, `develop`, `serve`, `start`)
- Starts the Spotlight sidecar on the specified port (or dynamic with `-p 0`)
- Streams events to stdout in JSON format (with `-f json`)
- Sets `SENTRY_SPOTLIGHT` env var for the child process
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Cursor AI planning file accidentally committed to repo

The .cursor/plans/ directory contains an internal Cursor AI development planning file that appears to have been accidentally committed. This file is not in .gitignore and contains internal development notes, TODO status tracking, and architecture diagrams that are typically local developer artifacts rather than production code.

Fix in Cursor Fix in Web

BYK added 2 commits December 12, 2025 15:48
Test apps are copied to a temp directory during CI, so they don't have
access to the root's node_modules where @spotlightjs/spotlight is
installed. Using npx will use the installed version if available or
download it if needed, working regardless of the package manager.
Comment on lines +158 to +161
const spotlightEnvValue =
globalWithInjectedValues.NEXT_PUBLIC_SENTRY_SPOTLIGHT ??
process.env._sentrySpotlight ??
globalWithInjectedValues._sentrySpotlight;

This comment was marked as outdated.

- Detects the start script from package.json (`dev`, `develop`, `serve`, `start`)
- Starts the Spotlight sidecar on the specified port (or dynamic with `-p 0`)
- Streams events to stdout in JSON format (with `-f json`)
- Sets `SENTRY_SPOTLIGHT` env var for the child process
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Cursor planning file accidentally included in commit

A Cursor AI development planning file was committed to the repository. The .cursor/plans/ directory contains internal development notes and task tracking that shouldn't be in the codebase. This file should be removed and the .cursor directory should likely be added to .gitignore.

Fix in Cursor Fix in Web

- Find monorepo root by looking for package.json with workspaces
- Use spotlight binary from root's node_modules/.bin/
- Fix regex to match 'Spotlight listening on PORT' format
- Resolve immediately when listening message detected

Tested locally - startSpotlight correctly detects port and starts.
- Detects the start script from package.json (`dev`, `develop`, `serve`, `start`)
- Starts the Spotlight sidecar on the specified port (or dynamic with `-p 0`)
- Streams events to stdout in JSON format (with `-f json`)
- Sets `SENTRY_SPOTLIGHT` env var for the child process
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Plan file accidentally committed to repository

A Cursor IDE plan file (.cursor/plans/migrate_e2e_to_spotlight_358ad839.plan.md) was accidentally committed to the repository. This is a development artifact containing internal planning notes and TODO items that should not be part of the production codebase. The file includes task status markers and implementation notes specific to a developer's local workflow.

Fix in Cursor Fix in Web

Use import.meta.url in ESM environments and __dirname in CJS.
This fixes the ReferenceError when tests run as ES modules.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(browser): Add environment variable support for Spotlight configuration

5 participants