Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

## 10.1.0

- feat(nuxt): Align build-time options to follow bundler plugins structure ([#17255](https://github.com/getsentry/sentry-javascript/pull/17255))
- fix(browser-utils): Ensure web vital client hooks unsubscribe correctly ([#17272](https://github.com/getsentry/sentry-javascript/pull/17272))
- fix(browser): Ensure request from `diagnoseSdkConnectivity` doesn't create span ([#17280](https://github.com/getsentry/sentry-javascript/pull/17280))

## 10.0.0

Version `10.0.0` marks a release of the Sentry JavaScript SDKs that contains breaking changes. The goal of this release is to primarily upgrade the underlying OpenTelemetry dependencies to v2 with minimal breaking changes.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://[email protected]/1337',
integrations: [Sentry.browserTracingIntegration({ idleTimeout: 3000, childSpanTimeout: 3000 })],
tracesSampleRate: 1,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Sentry.diagnoseSdkConnectivity().then(res => console.log('SDK connectivity:', res));
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { expect } from '@playwright/test';
import { sentryTest } from '../../../utils/fixtures';
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers';

sentryTest('makes a call to sentry.io to diagnose SDK connectivity', async ({ getLocalTestUrl, page }) => {
const bundle = process.env.PW_BUNDLE as string | undefined;
if (shouldSkipTracingTest() || !!bundle) {
// the CDN bundle doesn't export diagnoseSdkConnectivity. So skipping the test for bundles.
sentryTest.skip();
}

const pageloadRequestPromise = waitForTransactionRequest(page, e => e.contexts?.trace?.op === 'pageload');

// mock sdk connectivity url to avoid making actual request to sentry.io
page.route('**/api/4509632503087104/envelope/**/*', route => {
return route.fulfill({
status: 200,
body: '{}',
});
});

const diagnoseMessagePromise = new Promise<string>(resolve => {
page.on('console', msg => {
if (msg.text().includes('SDK connectivity:')) {
resolve(msg.text());
}
});
});

const url = await getLocalTestUrl({ testDir: __dirname });
await page.goto(url);

const pageLoadEvent = envelopeRequestParser(await pageloadRequestPromise);

// undefined is expected and means the request was successful
expect(await diagnoseMessagePromise).toEqual('SDK connectivity: undefined');

// the request to sentry.io should not be traced, hence no http.client span should be sent.
const httpClientSpans = pageLoadEvent.spans?.filter(s => s.op === 'http.client');
expect(httpClientSpans).toHaveLength(0);

// no fetch breadcrumb should be sent (only breadcrumb for the console log)
expect(pageLoadEvent.breadcrumbs).toEqual([
expect.objectContaining({
category: 'console',
message: 'SDK connectivity: undefined',
}),
]);
});
4 changes: 2 additions & 2 deletions packages/aws-serverless/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,11 +306,11 @@ export function wrapHandler<TEvent, TResult>(
if (options.captureAllSettledReasons && Array.isArray(rv) && isPromiseAllSettledResult(rv)) {
const reasons = getRejectedReasons(rv);
reasons.forEach(exception => {
captureException(exception, scope => markEventUnhandled(scope));
captureException(exception, scope => markEventUnhandled(scope, 'auto.function.aws-serverless.promise'));
});
}
} catch (e) {
captureException(e, scope => markEventUnhandled(scope));
captureException(e, scope => markEventUnhandled(scope, 'auto.function.aws-serverless.handler'));
throw e;
} finally {
clearTimeout(timeoutWarningTimer);
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-serverless/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ const headerGetter: TextMapGetter<APIGatewayProxyEventHeaders> = {
/**
* Marks an event as unhandled by adding a span processor to the passed scope.
*/
export function markEventUnhandled(scope: Scope): Scope {
export function markEventUnhandled(scope: Scope, type: string): Scope {
scope.addEventProcessor(event => {
addExceptionMechanism(event, { handled: false });
addExceptionMechanism(event, { handled: false, type });
return event;
});

Expand Down
2 changes: 1 addition & 1 deletion packages/aws-serverless/test/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ describe('AWSLambda', () => {
// @ts-expect-error just mocking around...
expect(evtProcessor(event).exception.values[0]?.mechanism).toEqual({
handled: false,
type: 'generic',
type: 'auto.function.aws-serverless.handler',
});
}
});
Expand Down
14 changes: 11 additions & 3 deletions packages/browser-utils/src/metrics/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,21 @@ export function listenForWebVitalReportEvents(
// we only want to collect LCP if we actually navigate. Redirects should be ignored.
if (!options?.isRedirect) {
_runCollectorCallbackOnce('navigation');
unsubscribeStartNavigation?.();
unsubscribeAfterStartPageLoadSpan?.();
safeUnsubscribe(unsubscribeStartNavigation, unsubscribeAfterStartPageLoadSpan);
}
});

const unsubscribeAfterStartPageLoadSpan = client.on('afterStartPageLoadSpan', span => {
pageloadSpanId = span.spanContext().spanId;
unsubscribeAfterStartPageLoadSpan?.();
safeUnsubscribe(unsubscribeAfterStartPageLoadSpan);
});
}

/**
* Invoke a list of unsubscribers in a safe way, by deferring the invocation to the next tick.
* This is necessary because unsubscribing in sync can lead to other callbacks no longer being invoked
* due to in-place array mutation of the subscribers array on the client.
*/
function safeUnsubscribe(...unsubscribers: (() => void | undefined)[]): void {
unsubscribers.forEach(u => u && setTimeout(u, 0));
}
32 changes: 17 additions & 15 deletions packages/browser/src/diagnose-sdk.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getClient } from '@sentry/core';
import { getClient, suppressTracing } from '@sentry/core';

/**
* A function to diagnose why the SDK might not be successfully sending data.
Expand All @@ -23,20 +23,22 @@ export async function diagnoseSdkConnectivity(): Promise<
}

try {
// If fetch throws, there is likely an ad blocker active or there are other connective issues.
await fetch(
// We are using the
// - "sentry-sdks" org with id 447951 not to pollute any actual organizations.
// - "diagnose-sdk-connectivity" project with id 4509632503087104
// - the public key of said org/project, which is disabled in the project settings
// => this DSN: https://[email protected]/4509632503087104 (i.e. disabled)
'https://o447951.ingest.sentry.io/api/4509632503087104/envelope/?sentry_version=7&sentry_key=c1dfb07d783ad5325c245c1fd3725390&sentry_client=sentry.javascript.browser%2F1.33.7',
{
body: '{}',
method: 'POST',
mode: 'cors',
credentials: 'omit',
},
await suppressTracing(() =>
// If fetch throws, there is likely an ad blocker active or there are other connective issues.
fetch(
// We are using the
// - "sentry-sdks" org with id 447951 not to pollute any actual organizations.
// - "diagnose-sdk-connectivity" project with id 4509632503087104
// - the public key of said org/project, which is disabled in the project settings
// => this DSN: https://[email protected]/4509632503087104 (i.e. disabled)
'https://o447951.ingest.sentry.io/api/4509632503087104/envelope/?sentry_version=7&sentry_key=c1dfb07d783ad5325c245c1fd3725390&sentry_client=sentry.javascript.browser%2F1.33.7',
{
body: '{}',
method: 'POST',
mode: 'cors',
credentials: 'omit',
},
),
);
} catch {
return 'sentry-unreachable';
Expand Down
14 changes: 14 additions & 0 deletions packages/browser/test/diagnose-sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,18 @@ describe('diagnoseSdkConnectivity', () => {
credentials: 'omit',
});
});

it('calls suppressTracing to avoid tracing the fetch call to sentry', async () => {
const suppressTracingSpy = vi.spyOn(sentryCore, 'suppressTracing');

const mockClient: Partial<Client> = {
getDsn: vi.fn().mockReturnValue('https://[email protected]/123'),
};
mockGetClient.mockReturnValue(mockClient);
mockFetch.mockResolvedValue(new Response('{}', { status: 200 }));

await diagnoseSdkConnectivity();

expect(suppressTracingSpy).toHaveBeenCalledTimes(1);
});
});
21 changes: 20 additions & 1 deletion packages/core/src/build-time-plugins/buildTimeOptionsBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,23 @@ export interface BuildTimeOptionsBase {
*/
silent?: boolean;

/**
* When an error occurs during release creation or sourcemaps upload, the plugin will call this function.
*
* By default, the plugin will simply throw an error, thereby stopping the bundling process.
* If an `errorHandler` callback is provided, compilation will continue unless an error is
* thrown in the provided callback.
*
* To allow compilation to continue but still emit a warning, set this option to the following:
*
* ```js
* (err) => {
* console.warn(err);
* }
* ```
*/
errorHandler?: (err: Error) => void;

/**
* Enable debug information logs about the SDK during build-time.
* Enabling this will give you, for example, logs about source maps.
Expand Down Expand Up @@ -184,7 +201,9 @@ export type UnstableRollupPluginOptions<PluginOptionsType> = {

interface SourceMapsOptions {
/**
* If this flag is `true`, any functionality related to source maps will be disabled.
* If this flag is `true`, any functionality related to source maps will be disabled. This includes the automatic upload of source maps.
*
* By default (`false`), the plugin automatically uploads source maps during a production build if a Sentry auth token is detected.
*
* @default false
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/nuxt/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module.exports = {
{
files: ['vite.config.ts'],
parserOptions: {
project: ['tsconfig.test.json'],
project: ['tsconfig.vite.json'],
},
},
],
Expand Down
34 changes: 27 additions & 7 deletions packages/nuxt/src/common/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { BuildTimeOptionsBase } from '@sentry/core';
import type { init as initNode } from '@sentry/node';
import type { SentryRollupPluginOptions } from '@sentry/rollup-plugin';
import type { SentryVitePluginOptions } from '@sentry/vite-plugin';
Expand Down Expand Up @@ -26,6 +27,7 @@ type SourceMapsOptions = {
* Suppresses all logs.
*
* @default false
* @deprecated Use option `silent` instead of `sourceMapsUploadOptions.silent`
*/
silent?: boolean;

Expand All @@ -43,13 +45,17 @@ type SourceMapsOptions = {
* console.warn(err);
* }
* ```
*
* @deprecated Use option `errorHandler` instead of `sourceMapsUploadOptions.errorHandler`
*/
errorHandler?: (err: Error) => void;

/**
* Options related to managing the Sentry releases for a build.
*
* More info: https://docs.sentry.io/product/releases/
*
* @deprecated Use option `release` instead of `sourceMapsUploadOptions.release`
*/
release?: {
/**
Expand All @@ -62,6 +68,8 @@ type SourceMapsOptions = {
* (the latter requires access to git CLI and for the root directory to be a valid repository)
*
* If you didn't provide a value and the plugin can't automatically detect one, no release will be created.
*
* @deprecated Use `release.name` instead of `sourceMapsUploadOptions.release.name`
*/
name?: string;
};
Expand All @@ -71,6 +79,7 @@ type SourceMapsOptions = {
* automatically generate and upload source maps to Sentry during a production build.
*
* @default true
* @deprecated Use option `sourcemaps.disable` instead of `sourceMapsUploadOptions.enabled`
*/
enabled?: boolean;

Expand All @@ -81,25 +90,30 @@ type SourceMapsOptions = {
*
* To create an auth token, follow this guide:
* @see https://docs.sentry.io/product/accounts/auth-tokens/#organization-auth-tokens
* @deprecated Use option `authToken` instead of `sourceMapsUploadOptions.authToken`
*/
authToken?: string;

/**
* The organization slug of your Sentry organization.
* Instead of specifying this option, you can also set the `SENTRY_ORG` environment variable.
* @deprecated Use option `org` instead of `sourceMapsUploadOptions.org`
*/
org?: string;

/**
* The URL of your Sentry instance if you're using self-hosted Sentry.
*
* @default https://sentry.io by default the plugin will point towards the Sentry SaaS URL
* @deprecated Use `sentryUrl` instead of `sourceMapsUploadOptions.url`
*/
url?: string;

/**
* The project slug of your Sentry project.
* Instead of specifying this option, you can also set the `SENTRY_PROJECT` environment variable.
*
* @deprecated Use option `project` instead of `sourceMapsUploadOptions.project`
*/
project?: string;

Expand All @@ -108,11 +122,14 @@ type SourceMapsOptions = {
* It will not collect any sensitive or user-specific data.
*
* @default true
* @deprecated Use option `telemetry` instead of `sourceMapsUploadOptions.telemetry`
*/
telemetry?: boolean;

/**
* Options related to sourcemaps
*
* @deprecated Use option `sourcemaps` instead of `sourceMapsUploadOptions.sourcemaps`
*/
sourcemaps?: {
/**
Expand All @@ -124,6 +141,8 @@ type SourceMapsOptions = {
*
* The globbing patterns must follow the implementation of the `glob` package.
* @see https://www.npmjs.com/package/glob#glob-primer
*
* @deprecated Use option `sourcemaps.assets` instead of `sourceMapsUploadOptions.sourcemaps.assets`
*/
assets?: string | Array<string>;

Expand All @@ -134,6 +153,8 @@ type SourceMapsOptions = {
* or the default value for `assets` are uploaded.
*
* The globbing patterns follow the implementation of the glob package. (https://www.npmjs.com/package/glob)
*
* @deprecated Use option `sourcemaps.ignore` instead of `sourceMapsUploadOptions.sourcemaps.ignore`
*/
ignore?: string | Array<string>;

Expand All @@ -144,6 +165,8 @@ type SourceMapsOptions = {
* @default [] - By default no files are deleted.
*
* The globbing patterns follow the implementation of the glob package. (https://www.npmjs.com/package/glob)
*
* @deprecated Use option `sourcemaps.filesToDeleteAfterUpload` instead of `sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload`
*/
filesToDeleteAfterUpload?: string | Array<string>;
};
Expand All @@ -152,7 +175,7 @@ type SourceMapsOptions = {
/**
* Build options for the Sentry module. These options are used during build-time by the Sentry SDK.
*/
export type SentryNuxtModuleOptions = {
export type SentryNuxtModuleOptions = BuildTimeOptionsBase & {
/**
* Enable the Sentry Nuxt Module.
*
Expand All @@ -165,15 +188,12 @@ export type SentryNuxtModuleOptions = {
*
* These options are always read from the `sentry` module options in the `nuxt.config.(js|ts).
* Do not define them in the `sentry.client.config.(js|ts)` or `sentry.server.config.(js|ts)` files.
*
* @deprecated This option was deprecated as it adds unnecessary nesting.
* Put the options one level higher to the root-level of the `sentry` module options.
*/
sourceMapsUploadOptions?: SourceMapsOptions;

/**
* Enable debug functionality of the SDK during build-time.
* Enabling this will give you, for example, logs about source maps.
*/
debug?: boolean;

/**
*
* Enables (partial) server tracing by automatically injecting Sentry for environments where modifying the node option `--import` is not possible.
Expand Down
Loading
Loading