diff --git a/develop-docs/sdk/expected-features/spotlight.mdx b/develop-docs/sdk/expected-features/spotlight.mdx new file mode 100644 index 00000000000000..119c3cc92b43f9 --- /dev/null +++ b/develop-docs/sdk/expected-features/spotlight.mdx @@ -0,0 +1,414 @@ +--- +title: Spotlight +sidebar_order: 5 +--- + +[Sentry Spotlight](https://spotlightjs.com/) is a local development tool that provides real-time observability for errors, traces, logs, and performance data during development. SDKs should implement support for Spotlight to enable developers to see their Sentry data locally without sending it to Sentry's servers. + +## Configuration Options + +SDKs should support Spotlight configuration through one of two approaches: + +### Single Attribute Approach (Recommended) + +The SDK accepts a single `spotlight` configuration attribute that can be: + +- `false` or `undefined`/`null`: Spotlight is disabled +- `true`: Spotlight is enabled using the default URL (`http://localhost:8969/stream`) +- `string`: Spotlight is enabled using the provided URL + +**Example:** + +```python +# Disabled +spotlight: False + +# Enabled with default URL +spotlight: True + +# Enabled with custom URL +spotlight: "http://localhost:3000/stream" +``` + +### Two-Attribute Approach (Alternative) + +The SDK accepts two separate configuration attributes: + +- `spotlight: Optional[boolean]`: Enable or disable Spotlight +- `spotlightUrl: Optional[string]`: Specify the Spotlight backend URL + +**Important:** If `spotlightUrl` is set to any truthy string value, it implies `spotlight: true` unless `spotlight` is explicitly set to `false`. + +**Example:** + +```python +# Disabled +spotlight: False + +# Enabled with default URL +spotlight: True + +# Enabled with custom URL (spotlightUrl implies spotlight: true) +spotlightUrl: "http://localhost:3000/stream" + +# Explicitly disabled even with spotlightUrl set +spotlight: False +spotlightUrl: "http://localhost:3000/stream" # Ignored +``` + +## Environment Variable Handling + +SDKs must support the `SENTRY_SPOTLIGHT` environment variable. The value should be parsed according to the following rules: + +### Truthy Values + +The following values should be treated as `spotlight: true` (enabling Spotlight with the default URL): + +- `"true"` +- `"t"` +- `"y"` +- `"yes"` +- `"on"` +- `"1"` + +### Falsy Values + +The following values should be treated as `spotlight: false` (disabling Spotlight): + +- `"false"` +- `"f"` +- `"n"` +- `"no"` +- `"off"` +- `"0"` + +### URL String Values + +Any other string value should be treated as a Spotlight backend URL, enabling Spotlight with that URL. + +**Example parsing logic (Python reference):** + +```python +def parse_spotlight_env(env_value): + if not env_value: + return None + + env_value_lower = env_value.lower().strip() + + # Truthy values + if env_value_lower in ("true", "t", "y", "yes", "on", "1"): + return True + + # Falsy values + if env_value_lower in ("false", "f", "n", "no", "off", "0"): + return False + + # Any other value is treated as a URL + return env_value +``` + +## Precedence Rules + +The interaction between configuration options and environment variables follows these precedence rules: + +1. **Config option takes precedence over env var**, except: + - If `spotlight: true` (boolean, no URL specified) AND `SENTRY_SPOTLIGHT` is set to a URL string → use the env var URL + - This allows developers to enable Spotlight via config but override the URL via environment variable + +2. **If `spotlight` is set to a string URL** → it overrides the env var completely + +3. **If using two-attribute approach:** + - If `spotlightUrl` config and env var are both set → prefer config value (`spotlightUrl`) + - If `spotlight: false` is explicitly set → ignore `spotlightUrl` value and the env var + +4. **If `spotlight: false` explicitly set** → ignore env var and any URL configuration + +**Precedence Examples:** + +```python +# Example 1: Config boolean true + env var URL → use env var URL +# Config: spotlight: True +# Env: SENTRY_SPOTLIGHT=http://custom:3000/stream +# Result: Enabled with http://custom:3000/stream + +# Example 2: Config URL string → always use config URL +# Config: spotlight: "http://config:3000/stream" +# Env: SENTRY_SPOTLIGHT=http://env:3000/stream +# Result: Enabled with http://config:3000/stream + +# Example 3: Config false → disable regardless of env var +# Config: spotlight: False +# Env: SENTRY_SPOTLIGHT=http://localhost:8969/stream +# Result: Disabled + +# Example 4: Two-attribute approach with spotlightUrl +# Config: spotlightUrl: "http://config:3000/stream" +# Env: SENTRY_SPOTLIGHT=http://env:3000/stream +# Result: Enabled with http://config:3000/stream (config takes precedence) +``` + +## Data Collection Behavior + +When Spotlight is enabled, SDKs must implement the following data collection behavior: + +### Envelope Transmission + +- Send a copy of **every envelope** to the Spotlight server +- This includes errors, transactions, sessions, profiles, replays, and all other envelope types + +### Sampling + +- Enable **100% sample rate** for the Spotlight pipeline +- This should **not affect** the upstream Sentry sample rates +- The ideal implementation uses a **separate pipeline** that ignores sampling settings +- Fallback: If separate pipeline is not feasible, enable sampling manually if no DSN is configured in development mode + +### PII Data Collection + +- Enable **all PII data collection** for Spotlight (equivalent to `sendDefaultPii: true`) +- This should **not affect** upstream Sentry PII settings +- Spotlight is intended for local development, so full data visibility is expected + +### Profiling and Logs + +- Enable profiling data transmission to Spotlight +- Enable log data transmission to Spotlight +- These should be sent regardless of upstream Sentry configuration + +### Pipeline Architecture + +**Ideal Implementation:** + +```python +# Separate pipeline that bypasses sampling +def send_to_spotlight(envelope): + # Clone envelope to avoid affecting upstream + spotlight_envelope = clone_envelope(envelope) + + # Override sampling - ensure 100% sample rate + spotlight_envelope.sample_rate = 1.0 + + # Enable all PII + spotlight_envelope.send_default_pii = True + + # Send to Spotlight server + spotlight_transport.send(spotlight_envelope) +``` + +**Fallback Implementation:** + +If separate pipeline is not feasible: + +```python +# Enable sampling if no DSN in development +if not dsn and is_development_mode(): + sample_rate = 1.0 + send_default_pii = True +``` + +## Default Values + +### Default Spotlight URL + +The default Spotlight backend URL is: + +``` +http://localhost:8969/stream +``` + +This URL should be used when: +- `spotlight: true` is set (boolean, no URL specified) +- `SENTRY_SPOTLIGHT` is set to a truthy value (not a URL string) + +### Default Behavior + +- Spotlight is **disabled by default** +- SDKs should only enable Spotlight when explicitly configured or when the environment variable is set + +## Error Handling + +SDKs must handle Spotlight server connectivity issues gracefully: + +### Unreachable Server + +- If the Spotlight server is unreachable, SDKs should: + - Log an error message (ideally once, not per-envelope) + - Implement exponential backoff retry logic + - Continue normal Sentry operation without interruption + +### Retry Logic + +**Recommended retry strategy:** + +```python +import time +import logging + +logger = logging.getLogger(__name__) + +class SpotlightTransport: + def __init__(self, url): + self.url = url + self.retry_delay = 1.0 # Start with 1 second + self.max_retry_delay = 60.0 # Max 60 seconds + self.error_logged = False + + def send(self, envelope): + try: + # Attempt to send + self._send_envelope(envelope) + # Reset retry delay on success + self.retry_delay = 1.0 + self.error_logged = False + except ConnectionError as e: + # Exponential backoff + if not self.error_logged: + logger.error(f"Spotlight server unreachable at {self.url}: {e}") + self.error_logged = True + + # Wait before retry + time.sleep(self.retry_delay) + self.retry_delay = min(self.retry_delay * 2, self.max_retry_delay) + + # Retry once, then give up for this envelope + try: + self._send_envelope(envelope) + self.retry_delay = 1.0 + except ConnectionError: + # Silently drop envelope after retry + pass +``` + +### Logging + +- Log errors at the appropriate level (typically `ERROR` or `WARNING`) +- Avoid logging errors for every failed envelope to prevent log spam +- Consider logging once per connection failure, then periodically if failures persist + +### Non-Blocking Behavior + +- Spotlight transmission must **never block** normal Sentry operation +- If Spotlight is unavailable, the SDK should continue sending data to Sentry normally +- Spotlight failures should not affect event capture, transaction recording, or any other SDK functionality + +## Implementation Examples + +### Python SDK Reference + +The Python SDK implementation serves as a reference. Key implementation details: + +- Single attribute approach: `spotlight: Optional[Union[str, bool]]` +- Environment variable: `SENTRY_SPOTLIGHT` +- Default URL: `http://localhost:8969/stream` +- Separate transport pipeline for Spotlight + +**Configuration Example:** + +```python +import sentry_sdk + +sentry_sdk.init( + dsn="https://...@sentry.io/...", + spotlight=True, # Enable with default URL + # or + spotlight="http://localhost:3000/stream", # Custom URL +) +``` + +### JavaScript/TypeScript SDK Pattern + +```typescript +interface SpotlightOptions { + spotlight?: boolean | string; + // or alternative: + // spotlight?: boolean; + // spotlightUrl?: string; +} + +function init(options: SpotlightOptions) { + const spotlightEnabled = resolveSpotlightConfig(options); + + if (spotlightEnabled) { + const spotlightUrl = typeof spotlightEnabled === 'string' + ? spotlightEnabled + : 'http://localhost:8969/stream'; + + setupSpotlightTransport(spotlightUrl); + } +} + +function resolveSpotlightConfig(options: SpotlightOptions): boolean | string | null { + // Config takes precedence + if (options.spotlight === false) { + return null; + } + + if (options.spotlight === true) { + // Check env var for URL override + const envValue = process.env.SENTRY_SPOTLIGHT; + if (envValue && !isTruthyFalsy(envValue)) { + return envValue; // Use env var URL + } + return true; // Use default URL + } + + if (typeof options.spotlight === 'string') { + return options.spotlight; // Config URL takes precedence + } + + // Check env var + const envValue = process.env.SENTRY_SPOTLIGHT; + if (envValue) { + return parseSpotlightEnv(envValue); + } + + return null; +} +``` + +### Java SDK Pattern + +```java +public class SpotlightConfig { + private Boolean enabled; + private String url; + + public static SpotlightConfig fromOptions(Options options, String envVar) { + SpotlightConfig config = new SpotlightConfig(); + + // Config takes precedence + if (options.getSpotlight() != null) { + if (options.getSpotlight() instanceof Boolean) { + config.enabled = (Boolean) options.getSpotlight(); + if (config.enabled && envVar != null && !isTruthyFalsy(envVar)) { + config.url = envVar; // Env var URL override + } + } else if (options.getSpotlight() instanceof String) { + config.enabled = true; + config.url = (String) options.getSpotlight(); + } + } else if (envVar != null) { + Object parsed = parseSpotlightEnv(envVar); + if (parsed instanceof Boolean) { + config.enabled = (Boolean) parsed; + } else if (parsed instanceof String) { + config.enabled = true; + config.url = (String) parsed; + } + } + + if (config.enabled == null || !config.enabled) { + return null; // Spotlight disabled + } + + config.url = config.url != null ? config.url : "http://localhost:8969/stream"; + return config; + } +} +``` + +## References + +- [Sentry Spotlight Documentation](https://spotlightjs.com/) +- [Sentry Spotlight GitHub Repository](https://github.com/getsentry/spotlight) +- [Python SDK Implementation Reference](https://github.com/getsentry/sentry-python/blob/master/sentry_sdk/utils.py)