diff --git a/build/build-bundle.js b/build/build-bundle.js index 2324a4bb26cf..5851b673a3e8 100644 --- a/build/build-bundle.js +++ b/build/build-bundle.js @@ -94,7 +94,7 @@ async function build(entryPath, distPath, opts = {minify: true}) { 'intl-pluralrules', 'intl', 'pako/lib/zlib/inflate.js', - 'raven', + '@sentry/node', 'source-map', 'ws', require.resolve('../lighthouse-core/gather/connections/cri.js'), diff --git a/lighthouse-cli/bin.js b/lighthouse-cli/bin.js index 7c1004f1a1b2..e70ddba108ae 100644 --- a/lighthouse-cli/bin.js +++ b/lighthouse-cli/bin.js @@ -137,12 +137,9 @@ async function begin() { url: urlUnderTest, flags: cliFlags, environmentData: { - name: 'redacted', // prevent sentry from using hostname + serverName: 'redacted', // prevent sentry from using hostname environment: isDev() ? 'development' : 'production', release: pkg.version, - tags: { - channel: 'cli', - }, }, }); } diff --git a/lighthouse-core/lib/sentry.js b/lighthouse-core/lib/sentry.js index 31e74a138e30..2cfc3d083904 100644 --- a/lighthouse-core/lib/sentry.js +++ b/lighthouse-core/lib/sentry.js @@ -7,8 +7,10 @@ const log = require('lighthouse-logger'); -/** @typedef {import('raven').CaptureOptions} CaptureOptions */ -/** @typedef {import('raven').ConstructorOptions} ConstructorOptions */ +/** @typedef {import('@sentry/node').Breadcrumb} Breadcrumb */ +/** @typedef {import('@sentry/node').NodeClient} NodeClient */ +/** @typedef {import('@sentry/node').NodeOptions} NodeOptions */ +/** @typedef {import('@sentry/node').Severity} Severity */ const SENTRY_URL = 'https://a6bb0da87ee048cc9ae2a345fc09ab2e:63a7029f46f74265981b7e005e0f69f8@sentry.io/174697'; @@ -21,7 +23,7 @@ const SAMPLED_ERRORS = [ // e.g.: {pattern: /No.*node with given id/, rate: 0.01}, ]; -const noop = () => {}; +const noop = () => { }; /** * A delegate for sentry so that environments without error reporting enabled will use @@ -29,14 +31,14 @@ const noop = () => {}; */ const sentryDelegate = { init, - /** @type {(message: string, options?: CaptureOptions) => void} */ + /** @type {(message: string, level?: Severity) => void} */ captureMessage: noop, - /** @type {(breadcrumb: any) => void} */ + /** @type {(breadcrumb: Breadcrumb) => void} */ captureBreadcrumb: noop, /** @type {() => any} */ getContext: noop, - /** @type {(error: Error, options?: CaptureOptions) => Promise} */ - captureException: async () => {}, + /** @type {(error: Error, options: {level?: string, tags?: {[key: string]: any}, extra?: {[key: string]: any}}) => Promise} */ + captureException: async () => { }, _shouldSample() { return SAMPLE_RATE >= Math.random(); }, @@ -44,7 +46,7 @@ const sentryDelegate = { /** * When called, replaces noops with actual Sentry implementation. - * @param {{url: string, flags: LH.CliFlags, environmentData: ConstructorOptions}} opts + * @param {{url: string, flags: LH.CliFlags, environmentData: NodeOptions}} opts */ function init(opts) { // If error reporting is disabled, leave the functions as a noop @@ -58,15 +60,25 @@ function init(opts) { } try { - const Sentry = require('raven'); - const sentryConfig = Object.assign({}, opts.environmentData, - {captureUnhandledRejections: true}); - Sentry.config(SENTRY_URL, sentryConfig).install(); + const Sentry = require('@sentry/node'); + Sentry.init({ + ...opts.environmentData, + dsn: SENTRY_URL, + }); + + const extras = { + ...opts.flags.throttling, + channel: opts.flags.channel || 'cli', + url: opts.url, + formFactor: opts.flags.formFactor, + throttlingMethod: opts.flags.throttlingMethod, + }; + Sentry.setExtras(extras); // Have each delegate function call the corresponding sentry function by default sentryDelegate.captureMessage = (...args) => Sentry.captureMessage(...args); - sentryDelegate.captureBreadcrumb = (...args) => Sentry.captureBreadcrumb(...args); - sentryDelegate.getContext = () => Sentry.getContext(); + sentryDelegate.captureBreadcrumb = (...args) => Sentry.addBreadcrumb(...args); + sentryDelegate.getContext = () => extras; // Keep a record of exceptions per audit/gatherer so we can just report once const sentryExceptionCache = new Map(); @@ -107,21 +119,24 @@ function init(opts) { opts.tags.protocolMethod = err.protocolMethod; } - return new Promise(resolve => { - Sentry.captureException(err, opts, () => resolve()); + Sentry.withScope(scope => { + if (opts.level) { + // @ts-expect-error - allow any string. + scope.setLevel(opts.level); + } + if (opts.tags) { + scope.setTags(opts.tags); + } + if (opts.extra) { + scope.setExtras(opts.extra); + } + Sentry.captureException(err); }); }; - - const context = Object.assign({ - url: opts.url, - formFactor: opts.flags.formFactor, - throttlingMethod: opts.flags.throttlingMethod, - }, opts.flags.throttling); - Sentry.mergeContext({extra: Object.assign({}, opts.environmentData.extra, context)}); } catch (e) { log.warn( 'sentry', - 'Could not load raven library, errors will not be reported.' + 'Could not load Sentry, errors will not be reported.' ); } } diff --git a/lighthouse-core/runner.js b/lighthouse-core/runner.js index 0507e6a9c7e8..83abbb0cff4c 100644 --- a/lighthouse-core/runner.js +++ b/lighthouse-core/runner.js @@ -149,7 +149,7 @@ class Runner { Sentry.captureBreadcrumb({ message: 'Run started', category: 'lifecycle', - data: sentryContext?.extra, + data: sentryContext, }); /** @type {LH.Artifacts} */ diff --git a/lighthouse-core/test/lib/sentry-test.js b/lighthouse-core/test/lib/sentry-test.js index 4e452234f81b..64c95b70979f 100644 --- a/lighthouse-core/test/lib/sentry-test.js +++ b/lighthouse-core/test/lib/sentry-test.js @@ -5,9 +5,9 @@ */ 'use strict'; -jest.mock('raven'); +jest.mock('@sentry/node'); -const raven = require('raven'); +const sentryNode = require('@sentry/node'); const Sentry = require('../../lib/sentry.js'); /* eslint-env jest */ @@ -27,9 +27,14 @@ describe('Sentry', () => { // We want to have a fresh state for every test. originalSentry = {...Sentry}; - raven.config = jest.fn().mockReturnValue({install: jest.fn()}); - raven.mergeContext = jest.fn(); - raven.captureException = jest.fn().mockImplementation((err, opts, cb) => cb()); + sentryNode.init = jest.fn().mockReturnValue({install: jest.fn()}); + sentryNode.setExtras = jest.fn(); + sentryNode.captureException = jest.fn(); + sentryNode.withScope = (fn) => fn({ + setLevel: () => {}, + setTags: () => {}, + setExtras: () => {}, + }); Sentry._shouldSample = jest.fn().mockReturnValue(true); }); @@ -41,18 +46,18 @@ describe('Sentry', () => { describe('.init', () => { it('should noop when !enableErrorReporting', () => { Sentry.init({url: 'http://example.com', flags: {}}); - expect(raven.config).not.toHaveBeenCalled(); + expect(sentryNode.init).not.toHaveBeenCalled(); Sentry.init({url: 'http://example.com', flags: {enableErrorReporting: false}}); - expect(raven.config).not.toHaveBeenCalled(); + expect(sentryNode.init).not.toHaveBeenCalled(); }); it('should noop when not picked for sampling', () => { Sentry._shouldSample.mockReturnValue(false); Sentry.init({url: 'http://example.com', flags: {enableErrorReporting: true}}); - expect(raven.config).not.toHaveBeenCalled(); + expect(sentryNode.init).not.toHaveBeenCalled(); }); - it('should initialize the raven client when enableErrorReporting', () => { + it('should initialize the Sentry client when enableErrorReporting', () => { Sentry.init({ url: 'http://example.com', flags: { @@ -63,26 +68,25 @@ describe('Sentry', () => { environmentData: {}, }); - expect(raven.config).toHaveBeenCalled(); - expect(raven.mergeContext).toHaveBeenCalled(); - expect(raven.mergeContext.mock.calls[0][0]).toEqual({ - extra: { - url: 'http://example.com', - formFactor: 'desktop', - throttlingMethod: 'devtools', - }, + expect(sentryNode.init).toHaveBeenCalled(); + expect(sentryNode.setExtras).toHaveBeenCalled(); + expect(sentryNode.setExtras.mock.calls[0][0]).toEqual({ + channel: 'cli', + url: 'http://example.com', + formFactor: 'desktop', + throttlingMethod: 'devtools', }); }); }); describe('.captureException', () => { - it('should forward exceptions to raven client', async () => { + it('should forward exceptions to Sentry client', async () => { Sentry.init(configPayload); const error = new Error('oops'); await Sentry.captureException(error); - expect(raven.captureException).toHaveBeenCalled(); - expect(raven.captureException.mock.calls[0][0]).toBe(error); + expect(sentryNode.captureException).toHaveBeenCalled(); + expect(sentryNode.captureException.mock.calls[0][0]).toBe(error); }); it('should skip expected errors', async () => { @@ -91,7 +95,7 @@ describe('Sentry', () => { error.expected = true; await Sentry.captureException(error); - expect(raven.captureException).not.toHaveBeenCalled(); + expect(sentryNode.captureException).not.toHaveBeenCalled(); }); it('should skip duplicate audit errors', async () => { @@ -100,7 +104,7 @@ describe('Sentry', () => { await Sentry.captureException(error, {tags: {audit: 'my-audit'}}); await Sentry.captureException(error, {tags: {audit: 'my-audit'}}); - expect(raven.captureException).toHaveBeenCalledTimes(1); + expect(sentryNode.captureException).toHaveBeenCalledTimes(1); }); it('should still allow different audit errors', async () => { @@ -110,7 +114,7 @@ describe('Sentry', () => { await Sentry.captureException(errorA, {tags: {audit: 'my-audit'}}); await Sentry.captureException(errorB, {tags: {audit: 'my-audit'}}); - expect(raven.captureException).toHaveBeenCalledTimes(2); + expect(sentryNode.captureException).toHaveBeenCalledTimes(2); }); it('should skip duplicate gatherer errors', async () => { @@ -119,7 +123,7 @@ describe('Sentry', () => { await Sentry.captureException(error, {tags: {gatherer: 'my-gatherer'}}); await Sentry.captureException(error, {tags: {gatherer: 'my-gatherer'}}); - expect(raven.captureException).toHaveBeenCalledTimes(1); + expect(sentryNode.captureException).toHaveBeenCalledTimes(1); }); }); }); diff --git a/package.json b/package.json index 4330f31f4132..0fe63b1b1c6c 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,6 @@ "@types/lodash.set": "^4.3.6", "@types/node": "*", "@types/pako": "^1.0.1", - "@types/raven": "^2.5.1", "@types/resize-observer-browser": "^0.1.1", "@types/semver": "^5.5.0", "@types/tabulator-tables": "^4.9.1", @@ -183,6 +182,7 @@ "webtreemap-cdt": "^3.2.1" }, "dependencies": { + "@sentry/node": "^6.17.4", "axe-core": "4.3.5", "chrome-launcher": "^0.15.0", "configstore": "^5.0.1", @@ -204,7 +204,6 @@ "open": "^8.4.0", "parse-cache-control": "1.0.1", "ps-list": "^8.0.0", - "raven": "^2.2.1", "robots-parser": "^3.0.0", "semver": "^5.3.0", "speedline-core": "^1.4.3", diff --git a/yarn.lock b/yarn.lock index 60d598516d91..17472eb94e9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1307,6 +1307,74 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@sentry/core@6.17.4": + version "6.17.4" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.17.4.tgz#ac23c2a9896b27fe4c532c2013c58c01f39adcdb" + integrity sha512-7QFgw+I9YK/X1Gie0c7phwT5pHMow66UCXHzDzHR2aK/0X3Lhn8OWlcGjIt5zmiBK/LHwNfQBNMskbktbYHgdA== + dependencies: + "@sentry/hub" "6.17.4" + "@sentry/minimal" "6.17.4" + "@sentry/types" "6.17.4" + "@sentry/utils" "6.17.4" + tslib "^1.9.3" + +"@sentry/hub@6.17.4": + version "6.17.4" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.17.4.tgz#af4f5f745340d676be023dc3038690b557111f4d" + integrity sha512-6+EvPcrPCwUmayeieIpm1ZrRNWriqMHWZFyw+MzunFLgG8IH8G45cJU1zNnTY9Jwwg4sFIS9xrHy3AOkctnIGw== + dependencies: + "@sentry/types" "6.17.4" + "@sentry/utils" "6.17.4" + tslib "^1.9.3" + +"@sentry/minimal@6.17.4": + version "6.17.4" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.17.4.tgz#6a35dbdb22a1c532d1eb7b4c0d9223618cb67ccd" + integrity sha512-p1A8UTtRt7bhV4ygu7yDNCannFr9E9dmqgeZWC7HrrTfygcnhNRFvTXTj92wEb0bFKuZr67wPSKnoXlkqkGxsw== + dependencies: + "@sentry/hub" "6.17.4" + "@sentry/types" "6.17.4" + tslib "^1.9.3" + +"@sentry/node@^6.17.4": + version "6.17.4" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-6.17.4.tgz#1207530e9d84c049ffffe070bc2bb8eba47bf21b" + integrity sha512-LpC07HsobBiFrNLe16ubgHGw95+7+3fEBhSn58r48j68c4Qak3fDmpR1Uy0rhyX1Nr/WFdlE/4npkgJw+1lN/w== + dependencies: + "@sentry/core" "6.17.4" + "@sentry/hub" "6.17.4" + "@sentry/tracing" "6.17.4" + "@sentry/types" "6.17.4" + "@sentry/utils" "6.17.4" + cookie "^0.4.1" + https-proxy-agent "^5.0.0" + lru_map "^0.3.3" + tslib "^1.9.3" + +"@sentry/tracing@6.17.4": + version "6.17.4" + resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.17.4.tgz#17c2ab50d9e4cdf727b9b25e7f91ae3a9ea19437" + integrity sha512-UV6wWH/fqndts0k0cptsNtzD0h8KXqHInJSCGqlWDlygFRO16jwMKv0wfXgqsgc3cBGDlsl8C4l6COSwz9ROdg== + dependencies: + "@sentry/hub" "6.17.4" + "@sentry/minimal" "6.17.4" + "@sentry/types" "6.17.4" + "@sentry/utils" "6.17.4" + tslib "^1.9.3" + +"@sentry/types@6.17.4": + version "6.17.4" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.17.4.tgz#36b78d7c4a6de19b2bbc631bb34893bcad30c0ba" + integrity sha512-RUyiXCKf61k2GIMP7FQX0naoSew4zLxe+UrtbjwVcWU4AFPZfH7tLNtTpVE85zAKbxsaiq3OD2FPtTZarHcwxQ== + +"@sentry/utils@6.17.4": + version "6.17.4" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.17.4.tgz#4f109629d2e7f16c5595b4367445ef47bfe96b61" + integrity sha512-+ENzZbrlVL1JJ+FoK2EOS27nbA/yToeaJPFlyVOnbthUxVyN3TTi9Uzn9F05fIE/2BTkOEk89wPtgcHafgrD6A== + dependencies: + "@sentry/types" "6.17.4" + tslib "^1.9.3" + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -1649,14 +1717,6 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0" integrity sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA== -"@types/raven@^2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@types/raven/-/raven-2.5.1.tgz#62ef0a59e29691945e1f295b62ed199619cbd9b6" - integrity sha512-pWEyOzD1VUXqVpDaZGsa1LnhZqXwTjMwVftNVU+nj7QOvRj+HvwUc4aVtljoj3CWGiBPUZyCTOHrtzvx56IQRQ== - dependencies: - "@types/events" "*" - "@types/node" "*" - "@types/resize-observer-browser@^0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.1.tgz#9b7cdae9cdc8b1a7020ca7588018dac64c770866" @@ -2855,10 +2915,10 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" -cookie@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= +cookie@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== copy-descriptor@^0.1.0: version "0.1.1" @@ -5735,10 +5795,10 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lsmod@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b" - integrity sha1-mgD3bco26yP6BTUK/htYXUKZ5ks= +lru_map@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" + integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0= lz-string@^1.4.4: version "1.4.4" @@ -6647,17 +6707,6 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -raven@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/raven/-/raven-2.2.1.tgz#57c7fbe68a80147ec527def3d7c01575cf948fe3" - integrity sha1-V8f75oqAFH7FJ97z18AVdc+Uj+M= - dependencies: - cookie "0.3.1" - lsmod "1.0.0" - stack-trace "0.0.9" - timed-out "4.0.1" - uuid "3.0.0" - rdf-canonize@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/rdf-canonize/-/rdf-canonize-3.0.0.tgz#f5bade563e5e58f5cc5881afcba3c43839e8c747" @@ -7280,11 +7329,6 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" -stack-trace@0.0.9: - version "0.0.9" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695" - integrity sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU= - stack-utils@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" @@ -7572,11 +7616,6 @@ through@2, "through@>=2.2.7 <3", through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -timed-out@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= - tmp@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" @@ -7715,6 +7754,11 @@ tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^1.9.3: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + tslib@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" @@ -7882,11 +7926,6 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -uuid@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728" - integrity sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg= - uuid@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"