Skip to content

Commit 5e920b8

Browse files
authored
Merge branch 'develop' into sig/next-bundler-optimization
2 parents 4fa73fd + 69a5e8e commit 5e920b8

File tree

8 files changed

+122
-29
lines changed

8 files changed

+122
-29
lines changed

.size-limit.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ module.exports = [
193193
import: createImport('init'),
194194
ignore: ['next/router', 'next/constants'],
195195
gzip: true,
196-
limit: '38 KB',
196+
limit: '38.03 KB',
197197
},
198198
// SvelteKit SDK (ESM)
199199
{

.vscode/settings.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@
3636
],
3737
"deno.enablePaths": ["packages/deno/test"],
3838
"editor.codeActionsOnSave": {
39-
"source.organizeImports.biome": "explicit",
39+
"source.organizeImports.biome": "explicit"
4040
},
4141
"editor.defaultFormatter": "biomejs.biome",
4242
"[typescript]": {
4343
"editor.defaultFormatter": "biomejs.biome"
44-
}
44+
},
45+
"cSpell.words": ["arrayify"]
4546
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { debugIntegration } from '@sentry/core';
2+
export { spotlightBrowserIntegration } from '../integrations/spotlight';
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { getNativeImplementation } from '@sentry-internal/browser-utils';
2+
import { defineIntegration } from '@sentry/core';
3+
import type { Client, Envelope, Event, IntegrationFn } from '@sentry/types';
4+
import { logger, serializeEnvelope } from '@sentry/utils';
5+
import type { WINDOW } from '../helpers';
6+
7+
import { DEBUG_BUILD } from '../debug-build';
8+
9+
export type SpotlightConnectionOptions = {
10+
/**
11+
* Set this if the Spotlight Sidecar is not running on localhost:8969
12+
* By default, the Url is set to http://localhost:8969/stream
13+
*/
14+
sidecarUrl?: string;
15+
};
16+
17+
export const INTEGRATION_NAME = 'SpotlightBrowser';
18+
19+
const _spotlightIntegration = ((options: Partial<SpotlightConnectionOptions> = {}) => {
20+
const sidecarUrl = options.sidecarUrl || 'http://localhost:8969/stream';
21+
22+
return {
23+
name: INTEGRATION_NAME,
24+
setup: () => {
25+
DEBUG_BUILD && logger.log('Using Sidecar URL', sidecarUrl);
26+
},
27+
// We don't want to send interaction transactions/root spans created from
28+
// clicks within Spotlight to Sentry. Neither do we want them to be sent to
29+
// spotlight.
30+
processEvent: event => (isSpotlightInteraction(event) ? null : event),
31+
afterAllSetup: (client: Client) => {
32+
setupSidecarForwarding(client, sidecarUrl);
33+
},
34+
};
35+
}) satisfies IntegrationFn;
36+
37+
function setupSidecarForwarding(client: Client, sidecarUrl: string): void {
38+
const makeFetch: typeof WINDOW.fetch | undefined = getNativeImplementation('fetch');
39+
let failCount = 0;
40+
41+
client.on('beforeEnvelope', (envelope: Envelope) => {
42+
if (failCount > 3) {
43+
logger.warn('[Spotlight] Disabled Sentry -> Spotlight integration due to too many failed requests:', failCount);
44+
return;
45+
}
46+
47+
makeFetch(sidecarUrl, {
48+
method: 'POST',
49+
body: serializeEnvelope(envelope),
50+
headers: {
51+
'Content-Type': 'application/x-sentry-envelope',
52+
},
53+
mode: 'cors',
54+
}).then(
55+
res => {
56+
if (res.status >= 200 && res.status < 400) {
57+
// Reset failed requests counter on success
58+
failCount = 0;
59+
}
60+
},
61+
err => {
62+
failCount++;
63+
logger.error(
64+
"Sentry SDK can't connect to Sidecar is it running? See: https://spotlightjs.com/sidecar/npx/",
65+
err,
66+
);
67+
},
68+
);
69+
});
70+
}
71+
72+
/**
73+
* Use this integration to send errors and transactions to Spotlight.
74+
*
75+
* Learn more about spotlight at https://spotlightjs.com
76+
*/
77+
export const spotlightBrowserIntegration = defineIntegration(_spotlightIntegration);
78+
79+
/**
80+
* Flags if the event is a transaction created from an interaction with the spotlight UI.
81+
*/
82+
export function isSpotlightInteraction(event: Event): boolean {
83+
return Boolean(
84+
event.type === 'transaction' &&
85+
event.spans &&
86+
event.contexts &&
87+
event.contexts.trace &&
88+
event.contexts.trace.op === 'ui.action.click' &&
89+
event.spans.some(({ description }) => description && description.includes('#sentry-spotlight')),
90+
);
91+
}

packages/core/src/baseclient.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,15 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
311311

312312
/** @inheritdoc */
313313
public init(): void {
314-
if (this._isEnabled()) {
314+
if (
315+
this._isEnabled() ||
316+
// Force integrations to be setup even if no DSN was set when we have
317+
// Spotlight enabled. This is particularly important for browser as we
318+
// don't support the `spotlight` option there and rely on the users
319+
// adding the `spotlightBrowserIntegration()` to their integrations which
320+
// wouldn't get initialized with the check below when there's no DSN set.
321+
this._options.integrations.some(({ name }) => name.startsWith('Spotlight'))
322+
) {
315323
this._setupIntegrations();
316324
}
317325
}

packages/core/src/integration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export type IntegrationIndex = {
1919

2020
/**
2121
* Remove duplicates from the given array, preferring the last instance of any duplicate. Not guaranteed to
22-
* preseve the order of integrations in the array.
22+
* preserve the order of integrations in the array.
2323
*
2424
* @private
2525
*/

packages/node/src/integrations/spotlight.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type SpotlightConnectionOptions = {
1111
sidecarUrl?: string;
1212
};
1313

14-
const INTEGRATION_NAME = 'Spotlight';
14+
export const INTEGRATION_NAME = 'Spotlight';
1515

1616
const _spotlightIntegration = ((options: Partial<SpotlightConnectionOptions> = {}) => {
1717
const _options = {
@@ -66,6 +66,10 @@ function connectToSpotlight(client: Client, options: Required<SpotlightConnectio
6666
},
6767
},
6868
res => {
69+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 400) {
70+
// Reset failed requests counter on success
71+
failedRequests = 0;
72+
}
6973
res.on('data', () => {
7074
// Drain socket
7175
});

packages/node/src/sdk/index.ts

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
setOpenTelemetryContextAsyncContextStrategy,
1818
setupEventContextTrace,
1919
} from '@sentry/opentelemetry';
20-
import type { Client, Integration, Options } from '@sentry/types';
20+
import type { Integration, Options } from '@sentry/types';
2121
import {
2222
consoleSandbox,
2323
dropUndefinedKeys,
@@ -36,7 +36,7 @@ import { modulesIntegration } from '../integrations/modules';
3636
import { nativeNodeFetchIntegration } from '../integrations/node-fetch';
3737
import { onUncaughtExceptionIntegration } from '../integrations/onuncaughtexception';
3838
import { onUnhandledRejectionIntegration } from '../integrations/onunhandledrejection';
39-
import { spotlightIntegration } from '../integrations/spotlight';
39+
import { INTEGRATION_NAME as SPOTLIGHT_INTEGRATION_NAME, spotlightIntegration } from '../integrations/spotlight';
4040
import { getAutoPerformanceIntegrations } from '../integrations/tracing';
4141
import { makeNodeTransport } from '../transports';
4242
import type { NodeClientOptions, NodeOptions } from '../types';
@@ -140,13 +140,19 @@ function _init(
140140
const scope = getCurrentScope();
141141
scope.update(options.initialScope);
142142

143+
if (options.spotlight && !options.integrations.some(({ name }) => name === SPOTLIGHT_INTEGRATION_NAME)) {
144+
options.integrations.push(
145+
spotlightIntegration({
146+
sidecarUrl: typeof options.spotlight === 'string' ? options.spotlight : undefined,
147+
}),
148+
);
149+
}
150+
143151
const client = new NodeClient(options);
144152
// The client is on the current scope, from where it generally is inherited
145153
getCurrentScope().setClient(client);
146154

147-
if (isEnabled(client)) {
148-
client.init();
149-
}
155+
client.init();
150156

151157
logger.log(`Running in ${isCjs() ? 'CommonJS' : 'ESM'} mode.`);
152158

@@ -158,20 +164,6 @@ function _init(
158164

159165
updateScopeFromEnvVariables();
160166

161-
if (options.spotlight) {
162-
// force integrations to be setup even if no DSN was set
163-
// If they have already been added before, they will be ignored anyhow
164-
const integrations = client.getOptions().integrations;
165-
for (const integration of integrations) {
166-
client.addIntegration(integration);
167-
}
168-
client.addIntegration(
169-
spotlightIntegration({
170-
sidecarUrl: typeof options.spotlight === 'string' ? options.spotlight : undefined,
171-
}),
172-
);
173-
}
174-
175167
// If users opt-out of this, they _have_ to set up OpenTelemetry themselves
176168
// There is no way to use this SDK without OpenTelemetry!
177169
if (!options.skipOpenTelemetrySetup) {
@@ -336,7 +328,3 @@ function startSessionTracking(): void {
336328
}
337329
});
338330
}
339-
340-
function isEnabled(client: Client): boolean {
341-
return client.getOptions().enabled !== false && client.getTransport() !== undefined;
342-
}

0 commit comments

Comments
 (0)