Skip to content
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

feat: Add spotlightBrowser integration #13263

Merged
merged 14 commits into from
Aug 12, 2024
5 changes: 3 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@
],
"deno.enablePaths": ["packages/deno/test"],
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"editor.defaultFormatter": "biomejs.biome",
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
}
},
"cSpell.words": ["arrayify"]
}
1 change: 1 addition & 0 deletions packages/browser/src/integrations-bundle/index.debug.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { debugIntegration } from '@sentry/core';
export { spotlightBrowser } from '../integrations/spotlight';
100 changes: 100 additions & 0 deletions packages/browser/src/integrations/spotlight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { getNativeImplementation } from '@sentry-internal/browser-utils';
import { defineIntegration } from '@sentry/core';
import type { Client, Envelope, Event, IntegrationFn } from '@sentry/types';
import { logger, serializeEnvelope } from '@sentry/utils';
import type { WINDOW } from '../helpers';

import { DEBUG_BUILD } from '../debug-build';

export type SpotlightConnectionOptions = {
/**
* Set this if the Spotlight Sidecar is not running on localhost:8969
* By default, the Url is set to http://localhost:8969/stream
*/
sidecarUrl?: string;
};

export const INTEGRATION_NAME = 'SpotlightBrowser';

const _spotlightIntegration = ((options: Partial<SpotlightConnectionOptions> = {}) => {
const sidecarUrl = options.sidecarUrl || 'http://localhost:8969/stream';

return {
name: INTEGRATION_NAME,
setupOnce: () => {
/* Empty function to ensure compatibility w/ JS SDK v7 >= 7.99.0 */
},
setup: () => {
DEBUG_BUILD && logger.log('Using Sidecar URL', sidecarUrl);
},
processEvent: event => {
// We don't want to send interaction transactions/root spans created from
// clicks within Spotlight to Sentry. Neither do we want them to be sent to
// spotlight.
if (isSpotlightInteraction(event)) {
return null;
}

if (event.type || !event.exception || !event.exception.values) {
return event;
}

return event;
},
afterAllSetup: (client: Client) => {
setupSidecarForwarding(client, sidecarUrl);
},
};
}) satisfies IntegrationFn;

function setupSidecarForwarding(client: Client, sidecarUrl: string): void {
const makeFetch: typeof WINDOW.fetch | undefined = getNativeImplementation('fetch');
let failCount = 0;

client.on('beforeEnvelope', (envelope: Envelope) => {
if (failCount > 3) {
logger.warn('[Spotlight] Disabled Sentry -> Spotlight integration due to too many failed requests:', failCount);
return;
}

makeFetch(sidecarUrl, {
method: 'POST',
body: serializeEnvelope(envelope),
headers: {
'Content-Type': 'application/x-sentry-envelope',
},
mode: 'cors',
}).then(
// Reset fail count on success
() => (failCount = 0),
err => {
failCount++;
logger.error(
"Sentry SDK can't connect to Sidecar is it running? See: https://spotlightjs.com/sidecar/npx/",
err,
);
},
);
});
}

/**
* Use this integration to send errors and transactions to Spotlight.
*
* Learn more about spotlight at https://spotlightjs.com
*/
export const spotlightBrowser = defineIntegration(_spotlightIntegration);

/**
* Flags if the event is a transaction created from an interaction with the spotlight UI.
*/
export function isSpotlightInteraction(event: Event): boolean {
return Boolean(
event.type === 'transaction' &&
event.spans &&
event.contexts &&
event.contexts.trace &&
event.contexts.trace.op === 'ui.action.click' &&
event.spans.some(({ description }) => description && description.includes('#sentry-spotlight')),
);
}
3 changes: 3 additions & 0 deletions packages/browser/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ export function init(browserOptions: BrowserOptions = {}): Client | undefined {
return;
}

if (options.spotlight) {
logger.error('You cannot use `spotlight: true` in the browser SDKs. Please use SpotlightBrowser() integration.');
}
if (DEBUG_BUILD) {
if (!supportsFetch()) {
logger.warn(
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type IntegrationIndex = {

/**
* Remove duplicates from the given array, preferring the last instance of any duplicate. Not guaranteed to
* preseve the order of integrations in the array.
* preserve the order of integrations in the array.
*
* @private
*/
Expand Down
2 changes: 2 additions & 0 deletions packages/node/src/integrations/spotlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ function connectToSpotlight(client: Client, options: Required<SpotlightConnectio

res.on('end', () => {
// Drain socket
// Reset failed requests counter on success
failedRequests = 0;
});
res.setEncoding('utf8');
},
Expand Down
7 changes: 7 additions & 0 deletions packages/types/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ export interface ClientOptions<TO extends BaseTransportOptions = BaseTransportOp
[key: string]: any;
};

/**
* Only works for Node or backend SDKs as we need Spotlight integration separately to keep the
* bundle tree-shakable. Adding here so we can warn if someone sets this to `true` on the browser
* SDKs, we can warn them about the "right way" (through integrations)
*/
spotlight?: boolean | string;

/**
* A pattern for error URLs which should exclusively be sent to Sentry.
* This is the opposite of {@link Options.denyUrls}.
Expand Down
Loading