From feca47c836ba977157890c20646ca6935a51eaa8 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 3 Sep 2024 14:51:49 +0200 Subject: [PATCH 1/5] fix(utils): Keep logger on carrier Today, if you have a pluggable integration, it will incorrectly use it's own logger, which will generally be disabled. This is because we inline all the logger code into the bundle, but the logger is never enabled. --- .../suites/feedback/attachTo/init.js | 20 +++++ .../suites/feedback/attachTo/template.html | 9 ++ .../suites/feedback/attachTo/test.ts | 82 +++++++++++++++++++ .../suites/feedback/logger/init.js | 22 +++++ .../suites/feedback/logger/test.ts | 35 ++++++++ .../httpclient/httpClientIntegration/init.js | 11 --- .../httpClientIntegration/subject.js | 8 -- .../httpclient/httpClientIntegration/test.ts | 64 --------------- .../suites/replay/logger/init.js | 18 ++++ .../suites/replay/logger/test.ts | 35 ++++++++ .../utils/generatePlugin.ts | 1 - packages/utils/src/logger.ts | 17 +++- packages/utils/src/worldwide.ts | 2 + 13 files changed, 238 insertions(+), 86 deletions(-) create mode 100644 dev-packages/browser-integration-tests/suites/feedback/attachTo/init.js create mode 100644 dev-packages/browser-integration-tests/suites/feedback/attachTo/template.html create mode 100644 dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/feedback/logger/init.js create mode 100644 dev-packages/browser-integration-tests/suites/feedback/logger/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/replay/logger/init.js create mode 100644 dev-packages/browser-integration-tests/suites/replay/logger/test.ts diff --git a/dev-packages/browser-integration-tests/suites/feedback/attachTo/init.js b/dev-packages/browser-integration-tests/suites/feedback/attachTo/init.js new file mode 100644 index 000000000000..fb1ec7133556 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/attachTo/init.js @@ -0,0 +1,20 @@ +import * as Sentry from '@sentry/browser'; +// Import this separately so that generatePlugin can handle it for CDN scenarios +import { feedbackIntegration } from '@sentry/browser'; + +const feedback = feedbackIntegration({ + autoInject: false, +}); + +window.Sentry = Sentry; +window.feedback = feedback; + + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + feedback, + ], +}); + +feedback.attachTo('#custom-feedback-buttom'); diff --git a/dev-packages/browser-integration-tests/suites/feedback/attachTo/template.html b/dev-packages/browser-integration-tests/suites/feedback/attachTo/template.html new file mode 100644 index 000000000000..ae36b0c69c7b --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/attachTo/template.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts b/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts new file mode 100644 index 000000000000..507b08685092 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts @@ -0,0 +1,82 @@ +import { expect } from '@playwright/test'; + +import { TEST_HOST, sentryTest } from '../../../utils/fixtures'; +import { envelopeRequestParser, getEnvelopeType, shouldSkipFeedbackTest } from '../../../utils/helpers'; + +sentryTest('should capture feedback with custom button', async ({ getLocalTestUrl, page }) => { + if (shouldSkipFeedbackTest()) { + sentryTest.skip(); + } + + const feedbackRequestPromise = page.waitForResponse(res => { + const req = res.request(); + + const postData = req.postData(); + if (!postData) { + return false; + } + + try { + return getEnvelopeType(req) === 'feedback'; + } catch (err) { + return false; + } + }); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + await page.locator('#custom-feedback-buttom').click(); + await page.waitForSelector(':visible:text-is("Report a Bug")'); + + expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1); + await page.locator('[name="name"]').fill('Jane Doe'); + await page.locator('[name="email"]').fill('janedoe@example.org'); + await page.locator('[name="message"]').fill('my example feedback'); + await page.locator('[data-sentry-feedback] .btn--primary').click(); + + const feedbackEvent = envelopeRequestParser((await feedbackRequestPromise).request()); + expect(feedbackEvent).toEqual({ + type: 'feedback', + breadcrumbs: expect.any(Array), + contexts: { + feedback: { + contact_email: 'janedoe@example.org', + message: 'my example feedback', + name: 'Jane Doe', + source: 'widget', + url: `${TEST_HOST}/index.html`, + }, + trace: { + trace_id: expect.stringMatching(/\w{32}/), + span_id: expect.stringMatching(/\w{16}/), + }, + }, + level: 'info', + timestamp: expect.any(Number), + event_id: expect.stringMatching(/\w{32}/), + environment: 'production', + tags: {}, + sdk: { + integrations: expect.arrayContaining(['Feedback']), + version: expect.any(String), + name: 'sentry.javascript.browser', + packages: expect.anything(), + }, + request: { + url: `${TEST_HOST}/index.html`, + headers: { + 'User-Agent': expect.stringContaining(''), + }, + }, + platform: 'javascript', + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/feedback/logger/init.js b/dev-packages/browser-integration-tests/suites/feedback/logger/init.js new file mode 100644 index 000000000000..de19e3930e39 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/logger/init.js @@ -0,0 +1,22 @@ +import * as Sentry from '@sentry/browser'; +// Import this separately so that generatePlugin can handle it for CDN scenarios +import { feedbackIntegration } from '@sentry/browser'; + +const feedback = feedbackIntegration({ + autoInject: false, +}); + +window.Sentry = Sentry; +window.feedback = feedback; + + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + debug: true, + integrations: [ + feedback, + ], +}); + +// This should log an error! +feedback.attachTo('#does-not-exist'); diff --git a/dev-packages/browser-integration-tests/suites/feedback/logger/test.ts b/dev-packages/browser-integration-tests/suites/feedback/logger/test.ts new file mode 100644 index 000000000000..1465204b7c69 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/logger/test.ts @@ -0,0 +1,35 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../utils/fixtures'; +import { shouldSkipFeedbackTest } from '../../../utils/helpers'; + +/** + * This test is mostly relevant for ensuring that the logger works in all combinations of CDN bundles. + * Even if feedback is included via the CDN, this test ensures that the logger is working correctly. + */ +sentryTest('should log error correctly', async ({ getLocalTestUrl, page }) => { + if (shouldSkipFeedbackTest()) { + sentryTest.skip(); + } + + const messages: string[] = []; + + page.on('console', message => { + messages.push(message.text()); + }); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.goto(url); + + expect(messages).toContain('Sentry Logger [log]: Integration installed: Feedback'); + expect(messages).toContain('Sentry Logger [error]: [Feedback] Unable to attach to target element'); +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/init.js deleted file mode 100644 index 8540ab176c38..000000000000 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/init.js +++ /dev/null @@ -1,11 +0,0 @@ -import * as Sentry from '@sentry/browser'; -import { httpClientIntegration } from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [httpClientIntegration()], - tracesSampleRate: 1, - sendDefaultPii: true, -}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/subject.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/subject.js deleted file mode 100644 index 563b069e66cc..000000000000 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/subject.js +++ /dev/null @@ -1,8 +0,0 @@ -const xhr = new XMLHttpRequest(); - -xhr.open('GET', 'http://sentry-test.io/foo', true); -xhr.withCredentials = true; -xhr.setRequestHeader('Accept', 'application/json'); -xhr.setRequestHeader('Content-Type', 'application/json'); -xhr.setRequestHeader('Cache', 'no-cache'); -xhr.send(); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/test.ts b/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/test.ts deleted file mode 100644 index f064a8652b48..000000000000 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/httpClientIntegration/test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { expect } from '@playwright/test'; -import type { Event } from '@sentry/types'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; - -sentryTest('works with httpClientIntegration', async ({ getLocalTestPath, page }) => { - const url = await getLocalTestPath({ testDir: __dirname }); - - await page.route('**/foo', route => { - return route.fulfill({ - status: 500, - body: JSON.stringify({ - error: { - message: 'Internal Server Error', - }, - }), - headers: { - 'Content-Type': 'text/html', - }, - }); - }); - - const eventData = await getFirstSentryEnvelopeRequest(page, url); - - expect(eventData.exception?.values).toHaveLength(1); - - // Not able to get the cookies from the request/response because of Playwright bug - // https://github.com/microsoft/playwright/issues/11035 - expect(eventData).toMatchObject({ - message: 'HTTP Client Error with status code: 500', - exception: { - values: [ - { - type: 'Error', - value: 'HTTP Client Error with status code: 500', - mechanism: { - type: 'http.client', - handled: false, - }, - }, - ], - }, - request: { - url: 'http://sentry-test.io/foo', - method: 'GET', - headers: { - accept: 'application/json', - cache: 'no-cache', - 'content-type': 'application/json', - }, - }, - contexts: { - response: { - status_code: 500, - body_size: 45, - headers: { - 'content-type': 'text/html', - 'content-length': '45', - }, - }, - }, - }); -}); diff --git a/dev-packages/browser-integration-tests/suites/replay/logger/init.js b/dev-packages/browser-integration-tests/suites/replay/logger/init.js new file mode 100644 index 000000000000..195be16ddad3 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/replay/logger/init.js @@ -0,0 +1,18 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; +window.Replay = Sentry.replayIntegration({ + flushMinDelay: 200, + flushMaxDelay: 200, + minReplayDuration: 0, +}); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + sampleRate: 0, + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, + debug: true, + + integrations: [window.Replay], +}); diff --git a/dev-packages/browser-integration-tests/suites/replay/logger/test.ts b/dev-packages/browser-integration-tests/suites/replay/logger/test.ts new file mode 100644 index 000000000000..9b054323c3b7 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/replay/logger/test.ts @@ -0,0 +1,35 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../utils/fixtures'; +import { shouldSkipReplayTest, waitForReplayRequest } from '../../../utils/replayHelpers'; + +sentryTest('should output logger messages', async ({ getLocalTestPath, page }) => { + if (shouldSkipReplayTest()) { + sentryTest.skip(); + } + + const messages: string[] = []; + + page.on('console', message => { + messages.push(message.text()); + }); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const reqPromise0 = waitForReplayRequest(page, 0); + + const url = await getLocalTestPath({ testDir: __dirname }); + + await Promise.all([page.goto(url), reqPromise0]); + + expect(messages).toContain('Sentry Logger [log]: Integration installed: Replay'); + expect(messages).toContain('Sentry Logger [info]: [Replay] Creating new session'); + expect(messages).toContain('Sentry Logger [info]: [Replay] Starting replay in session mode'); + expect(messages).toContain('Sentry Logger [info]: [Replay] Using compression worker'); +}); diff --git a/dev-packages/browser-integration-tests/utils/generatePlugin.ts b/dev-packages/browser-integration-tests/utils/generatePlugin.ts index 9189ce63812f..acc583506df4 100644 --- a/dev-packages/browser-integration-tests/utils/generatePlugin.ts +++ b/dev-packages/browser-integration-tests/utils/generatePlugin.ts @@ -30,7 +30,6 @@ const useLoader = bundleKey.startsWith('loader'); const IMPORTED_INTEGRATION_CDN_BUNDLE_PATHS: Record = { httpClientIntegration: 'httpclient', captureConsoleIntegration: 'captureconsole', - CaptureConsole: 'captureconsole', debugIntegration: 'debug', rewriteFramesIntegration: 'rewriteframes', contextLinesIntegration: 'contextlines', diff --git a/packages/utils/src/logger.ts b/packages/utils/src/logger.ts index e996d87202b2..0682e99f3b62 100644 --- a/packages/utils/src/logger.ts +++ b/packages/utils/src/logger.ts @@ -1,7 +1,7 @@ import type { ConsoleLevel } from '@sentry/types'; import { DEBUG_BUILD } from './debug-build'; -import { GLOBAL_OBJ } from './worldwide'; +import { GLOBAL_OBJ, getGlobalSingleton } from './worldwide'; /** Prefix for logging strings */ const PREFIX = 'Sentry Logger '; @@ -97,4 +97,17 @@ function makeLogger(): Logger { return logger as Logger; } -export const logger = makeLogger(); +/** + * This is a logger singleton which either logs things or no-ops if logging is not enabled. + * The logger is a singleton on the carrier, to ensure that a consistent logger is used throughout the SDK. + */ +export const logger: Logger = new Proxy( + {}, + { + get: (_target, prop: ConsoleLevel) => { + const logger = getGlobalSingleton('logger', makeLogger); + + return logger[prop]; + }, + }, +) as Logger; diff --git a/packages/utils/src/worldwide.ts b/packages/utils/src/worldwide.ts index e323f12034a2..2a1ca7b958d8 100644 --- a/packages/utils/src/worldwide.ts +++ b/packages/utils/src/worldwide.ts @@ -15,6 +15,7 @@ import type { Client, MetricsAggregator, Scope } from '@sentry/types'; import type { SdkSource } from './env'; +import type { logger } from './logger'; import { SDK_VERSION } from './version'; interface SentryCarrier { @@ -25,6 +26,7 @@ interface SentryCarrier { defaultIsolationScope?: Scope; defaultCurrentScope?: Scope; globalMetricsAggregators?: WeakMap | undefined; + logger?: typeof logger; /** Overwrites TextEncoder used in `@sentry/utils`, need for `react-native@0.73` and older */ encodePolyfill?: (input: string) => Uint8Array; From a225a43eab7ca558e9c815b1a7ba1f19c73af465 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 3 Sep 2024 15:01:58 +0200 Subject: [PATCH 2/5] user singleton directly --- packages/utils/src/logger.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/utils/src/logger.ts b/packages/utils/src/logger.ts index 0682e99f3b62..533b59fd5882 100644 --- a/packages/utils/src/logger.ts +++ b/packages/utils/src/logger.ts @@ -101,13 +101,4 @@ function makeLogger(): Logger { * This is a logger singleton which either logs things or no-ops if logging is not enabled. * The logger is a singleton on the carrier, to ensure that a consistent logger is used throughout the SDK. */ -export const logger: Logger = new Proxy( - {}, - { - get: (_target, prop: ConsoleLevel) => { - const logger = getGlobalSingleton('logger', makeLogger); - - return logger[prop]; - }, - }, -) as Logger; +export const logger = getGlobalSingleton('logger', makeLogger); From 52ab4bd49e21d4d5d9f751488bcbab5a5dc64963 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 3 Sep 2024 15:31:51 +0200 Subject: [PATCH 3/5] add new size limits --- .size-limit.js | 19 +++++++++++++++++++ .../suites/feedback/attachTo/init.js | 5 +---- .../suites/feedback/logger/init.js | 5 +---- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index d2211d91d5b0..fea0dc49b559 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -10,6 +10,25 @@ module.exports = [ gzip: true, limit: '24 KB', }, + { + name: '@sentry/browser - with treeshaking flags', + path: 'packages/browser/build/npm/esm/index.js', + import: createImport('init'), + gzip: true, + limit: '24 KB', + modifyWebpackConfig: function (config) { + const webpack = require('webpack'); + config.plugins.push( + new webpack.DefinePlugin({ + __SENTRY_DEBUG__: false, + __RRWEB_EXCLUDE_SHADOW_DOM__: true, + __RRWEB_EXCLUDE_IFRAME__: true, + __SENTRY_EXCLUDE_REPLAY_WORKER__: true, + }), + ); + return config; + }, + }, { name: '@sentry/browser (incl. Tracing)', path: 'packages/browser/build/npm/esm/index.js', diff --git a/dev-packages/browser-integration-tests/suites/feedback/attachTo/init.js b/dev-packages/browser-integration-tests/suites/feedback/attachTo/init.js index fb1ec7133556..5eb27143fdc7 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/attachTo/init.js +++ b/dev-packages/browser-integration-tests/suites/feedback/attachTo/init.js @@ -9,12 +9,9 @@ const feedback = feedbackIntegration({ window.Sentry = Sentry; window.feedback = feedback; - Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [ - feedback, - ], + integrations: [feedback], }); feedback.attachTo('#custom-feedback-buttom'); diff --git a/dev-packages/browser-integration-tests/suites/feedback/logger/init.js b/dev-packages/browser-integration-tests/suites/feedback/logger/init.js index de19e3930e39..3251bd6c7a4c 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/logger/init.js +++ b/dev-packages/browser-integration-tests/suites/feedback/logger/init.js @@ -9,13 +9,10 @@ const feedback = feedbackIntegration({ window.Sentry = Sentry; window.feedback = feedback; - Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', debug: true, - integrations: [ - feedback, - ], + integrations: [feedback], }); // This should log an error! From 4386e420291161cbe8d60ec64ecc31302a689849 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 3 Sep 2024 15:56:42 +0200 Subject: [PATCH 4/5] remove size limit --- .size-limit.js | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index fea0dc49b559..d2211d91d5b0 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -10,25 +10,6 @@ module.exports = [ gzip: true, limit: '24 KB', }, - { - name: '@sentry/browser - with treeshaking flags', - path: 'packages/browser/build/npm/esm/index.js', - import: createImport('init'), - gzip: true, - limit: '24 KB', - modifyWebpackConfig: function (config) { - const webpack = require('webpack'); - config.plugins.push( - new webpack.DefinePlugin({ - __SENTRY_DEBUG__: false, - __RRWEB_EXCLUDE_SHADOW_DOM__: true, - __RRWEB_EXCLUDE_IFRAME__: true, - __SENTRY_EXCLUDE_REPLAY_WORKER__: true, - }), - ); - return config; - }, - }, { name: '@sentry/browser (incl. Tracing)', path: 'packages/browser/build/npm/esm/index.js', From 3fd1ec586c3bd6e1d220e5f95ec5daa4940d21e3 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 3 Sep 2024 16:59:46 +0200 Subject: [PATCH 5/5] skip in debug tests --- .../browser-integration-tests/suites/feedback/logger/test.ts | 3 ++- .../browser-integration-tests/suites/replay/logger/test.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/feedback/logger/test.ts b/dev-packages/browser-integration-tests/suites/feedback/logger/test.ts index 1465204b7c69..34fadfc2503b 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/logger/test.ts +++ b/dev-packages/browser-integration-tests/suites/feedback/logger/test.ts @@ -8,7 +8,8 @@ import { shouldSkipFeedbackTest } from '../../../utils/helpers'; * Even if feedback is included via the CDN, this test ensures that the logger is working correctly. */ sentryTest('should log error correctly', async ({ getLocalTestUrl, page }) => { - if (shouldSkipFeedbackTest()) { + // In minified bundles we do not have logger messages, so we skip the test + if (shouldSkipFeedbackTest() || (process.env.PW_BUNDLE || '').includes('_min')) { sentryTest.skip(); } diff --git a/dev-packages/browser-integration-tests/suites/replay/logger/test.ts b/dev-packages/browser-integration-tests/suites/replay/logger/test.ts index 9b054323c3b7..fa034a12b003 100644 --- a/dev-packages/browser-integration-tests/suites/replay/logger/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/logger/test.ts @@ -4,7 +4,8 @@ import { sentryTest } from '../../../utils/fixtures'; import { shouldSkipReplayTest, waitForReplayRequest } from '../../../utils/replayHelpers'; sentryTest('should output logger messages', async ({ getLocalTestPath, page }) => { - if (shouldSkipReplayTest()) { + // In minified bundles we do not have logger messages, so we skip the test + if (shouldSkipReplayTest() || (process.env.PW_BUNDLE || '').includes('_min')) { sentryTest.skip(); }