diff --git a/CHANGELOG.md b/CHANGELOG.md index 90f7704..3ad098f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- ref: Use latest Sentry v7 SDK for telemetry collection (#47) + ## 0.2.0 - feat: Add transform to extract node config into separate file (#41) diff --git a/package.json b/package.json index 1582bfa..161a0c3 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "dependencies": { "@clack/prompts": "^0.7.0", - "@sentry/node": "^7.64.0", + "@sentry/node": "^7.114.0", "chalk": "^5.3.0", "globby": "^14.0.1", "jscodeshift": "^0.15.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ff696e..44b6109 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ dependencies: specifier: ^0.7.0 version: 0.7.0 '@sentry/node': - specifier: ^7.64.0 - version: 7.64.0 + specifier: ^7.114.0 + version: 7.114.0 chalk: specifier: ^5.3.0 version: 5.3.0 @@ -1068,14 +1068,13 @@ packages: requireindex: 1.1.0 dev: true - /@sentry-internal/tracing@7.64.0: - resolution: {integrity: sha512-1XE8W6ki7hHyBvX9hfirnGkKDBKNq3bDJyXS86E0bYVDl94nvbRM9BD9DHsCFetqYkVm1yDGEK+6aUVs4CztoQ==} + /@sentry-internal/tracing@7.114.0: + resolution: {integrity: sha512-dOuvfJN7G+3YqLlUY4HIjyWHaRP8vbOgF+OsE5w2l7ZEn1rMAaUbPntAR8AF9GBA6j2zWNoSo8e7GjbJxVofSg==} engines: {node: '>=8'} dependencies: - '@sentry/core': 7.64.0 - '@sentry/types': 7.64.0 - '@sentry/utils': 7.64.0 - tslib: 2.6.2 + '@sentry/core': 7.114.0 + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 dev: false /@sentry-internal/typescript@7.64.0(typescript@4.9.5): @@ -1086,42 +1085,45 @@ packages: typescript: 4.9.5 dev: true - /@sentry/core@7.64.0: - resolution: {integrity: sha512-IzmEyl5sNG7NyEFiyFHEHC+sizsZp9MEw1+RJRLX6U5RITvcsEgcajSkHQFafaBPzRrcxZMdm47Cwhl212LXcw==} + /@sentry/core@7.114.0: + resolution: {integrity: sha512-YnanVlmulkjgZiVZ9BfY9k6I082n+C+LbZo52MTvx3FY6RE5iyiPMpaOh67oXEZRWcYQEGm+bKruRxLVP6RlbA==} engines: {node: '>=8'} dependencies: - '@sentry/types': 7.64.0 - '@sentry/utils': 7.64.0 - tslib: 2.6.2 + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 dev: false - /@sentry/node@7.64.0: - resolution: {integrity: sha512-wRi0uTnp1WSa83X2yLD49tV9QPzGh5e42IKdIDBiQ7lV9JhLILlyb34BZY1pq6p4dp35yDasDrP3C7ubn7wo6A==} + /@sentry/integrations@7.114.0: + resolution: {integrity: sha512-BJIBWXGKeIH0ifd7goxOS29fBA8BkEgVVCahs6xIOXBjX1IRS6PmX0zYx/GP23nQTfhJiubv2XPzoYOlZZmDxg==} engines: {node: '>=8'} dependencies: - '@sentry-internal/tracing': 7.64.0 - '@sentry/core': 7.64.0 - '@sentry/types': 7.64.0 - '@sentry/utils': 7.64.0 - cookie: 0.4.2 - https-proxy-agent: 5.0.1 - lru_map: 0.3.3 - tslib: 2.6.2 - transitivePeerDependencies: - - supports-color + '@sentry/core': 7.114.0 + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 + localforage: 1.10.0 + dev: false + + /@sentry/node@7.114.0: + resolution: {integrity: sha512-cqvi+OHV1Hj64mIGHoZtLgwrh1BG6ntcRjDLlVNMqml5rdTRD3TvG21579FtlqHlwZpbpF7K5xkwl8e5KL2hGw==} + engines: {node: '>=8'} + dependencies: + '@sentry-internal/tracing': 7.114.0 + '@sentry/core': 7.114.0 + '@sentry/integrations': 7.114.0 + '@sentry/types': 7.114.0 + '@sentry/utils': 7.114.0 dev: false - /@sentry/types@7.64.0: - resolution: {integrity: sha512-LqjQprWXjUFRmzIlUjyA+KL+38elgIYmAeoDrdyNVh8MK5IC1W2Lh1Q87b4yOiZeMiIhIVNBd7Ecoh2rodGrGA==} + /@sentry/types@7.114.0: + resolution: {integrity: sha512-tsqkkyL3eJtptmPtT0m9W/bPLkU7ILY7nvwpi1hahA5jrM7ppoU0IMaQWAgTD+U3rzFH40IdXNBFb8Gnqcva4w==} engines: {node: '>=8'} dev: false - /@sentry/utils@7.64.0: - resolution: {integrity: sha512-HRlM1INzK66Gt+F4vCItiwGKAng4gqzCR4C5marsL3qv6SrKH98dQnCGYgXluSWaaa56h97FRQu7TxCk6jkSvQ==} + /@sentry/utils@7.114.0: + resolution: {integrity: sha512-319N90McVpupQ6vws4+tfCy/03AdtsU0MurIE4+W5cubHME08HtiEWlfacvAxX+yuKFhvdsO4K4BB/dj54ideg==} engines: {node: '>=8'} dependencies: - '@sentry/types': 7.64.0 - tslib: 2.6.2 + '@sentry/types': 7.114.0 dev: false /@sinclair/typebox@0.27.8: @@ -1386,15 +1388,6 @@ packages: hasBin: true dev: true - /agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - dependencies: - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: false - /aggregate-error@4.0.1: resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} engines: {node: '>=12'} @@ -1732,11 +1725,6 @@ packages: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: false - /cookie@0.4.2: - resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} - engines: {node: '>= 0.6'} - dev: false - /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -2634,16 +2622,6 @@ packages: hasBin: true dev: false - /https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - dependencies: - agent-base: 6.0.2 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: false - /human-signals@3.0.1: resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} engines: {node: '>=12.20.0'} @@ -2662,6 +2640,10 @@ packages: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} + /immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + dev: false + /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -2964,6 +2946,12 @@ packages: type-check: 0.4.0 dev: true + /lie@3.1.1: + resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} + dependencies: + immediate: 3.0.6 + dev: false + /local-pkg@0.5.0: resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} @@ -2972,6 +2960,12 @@ packages: pkg-types: 1.1.1 dev: true + /localforage@1.10.0: + resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} + dependencies: + lie: 3.1.1 + dev: false + /locate-path@3.0.0: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} @@ -3017,10 +3011,6 @@ packages: dependencies: yallist: 4.0.0 - /lru_map@0.3.3: - resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} - dev: false - /magic-string@0.30.10: resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} dependencies: diff --git a/src/index.test.js b/src/index.test.js index ace5384..9e24f32 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -5,8 +5,8 @@ import { KEYS, WaitType, defaultRunner } from '../test-helpers/clet.js'; describe('index', () => { it('works with an empty app & default options', async () => { await defaultRunner('emptyApp') - .wait(WaitType.stdout, /Do you want to apply all transformers, or only selected ones/) - .stdin(/Do you want to apply all transformers, or only selected ones/, KEYS.ENTER) + .wait(WaitType.stdout, /Do you want to apply all code transforms, or only selected ones\?/) + .stdin(/Do you want to apply all code transforms, or only selected ones\?/, KEYS.ENTER) .wait(WaitType.stdout, /Transformer (.*) completed./) .wait(WaitType.stdout, /All transformers completed!/); }); diff --git a/src/run.js b/src/run.js index 77aa702..7883103 100644 --- a/src/run.js +++ b/src/run.js @@ -23,13 +23,15 @@ process.setMaxListeners(0); * @param {import("types").RunOptions} options */ export async function run(options) { - withTelemetry({ enabled: !options.disableTelemetry }, () => runWithTelementry(options)); + withTelemetry({ enabled: !options.disableTelemetry }, () => runWithTelemetry(options)); } /** * @param {import("types").RunOptions} options */ -export async function runWithTelementry(options) { +export async function runWithTelemetry(options) { + Sentry.metrics.increment('executions'); + traceStep('intro', () => { intro(chalk.inverse('Welcome to sentry-migr8!')); note( @@ -38,16 +40,18 @@ export async function runWithTelementry(options) { We will guide you through the process step by step.${ !options.disableTelemetry ? ` - This tool collectes anonymous telemetry data and sends it to Sentry. + This tool collects anonymous telemetry data and sends it to Sentry. You can disable this by passing the --disableTelemetry option.` : '' }` ); - note(`We will run transforms on files matching the following ${ + note(`We will run code transforms on files matching the following ${ options.filePatterns.length > 1 ? 'patterns' : 'pattern' - }, ignoring any gitignored files: + }, ignoring any .gitignored files: + ${options.filePatterns.join('\n')} + (You can change this by specifying the --filePatterns option)`); }); @@ -74,7 +78,7 @@ export async function runWithTelementry(options) { const applyAllTransformers = await traceStep('ask-apply-all-transformers', async () => abortIfCancelled( select({ - message: 'Do you want to apply all transformers, or only selected ones?', + message: 'Do you want to apply all code transforms, or only selected ones?', options: [ { value: true, label: 'Apply all transformations.', hint: 'Recommended' }, { value: false, label: 'I want to select myself.' }, @@ -83,7 +87,7 @@ export async function runWithTelementry(options) { ) ); - Sentry.setTag('apply-all-transformers', applyAllTransformers); + Sentry.metrics.distribution('apply-all-transformers', applyAllTransformers ? 1 : 0, { unit: 'percent' }); let transformers = allTransformers; if (!applyAllTransformers) { @@ -105,6 +109,7 @@ export async function runWithTelementry(options) { await traceStep('run-transformers', async () => { for (const transformer of transformers) { + Sentry.metrics.set('transforms.selected', transformer.name); const showSpinner = !transformer.requiresUserInput; const s = spinner(); @@ -117,12 +122,14 @@ export async function runWithTelementry(options) { if (showSpinner) { s.stop(`Transformer "${transformer.name}" completed.`); } + Sentry.metrics.set('transforms.success', transformer.name); } catch (error) { if (showSpinner) { s.stop(`Transformer "${transformer.name}" failed to complete with error:`); } // eslint-disable-next-line no-console console.error(error); + Sentry.metrics.set('transforms.fail', transformer.name); } } }); @@ -133,6 +140,8 @@ export async function runWithTelementry(options) { log.success('All transformers completed!'); outro('Thank you for using @sentry/migr8!'); }); + + Sentry.metrics.increment('executions.success'); } /** @@ -145,7 +154,7 @@ function detectSdk(packageJSON) { const sdkName = sdkPackage ? sdkPackage.name : undefined; if (sdkName) { - log.info(`Detected SDK: ${sdkName}`); + log.info(`Detected SDK: ${chalk.cyan(sdkName)}`); } else { log.info( `No Sentry SDK detected. Skipping all import-rewriting transformations. diff --git a/src/transformers/addMigrationComments/transform.cjs b/src/transformers/addMigrationComments/transform.cjs index b122c2b..0f40e74 100644 --- a/src/transformers/addMigrationComments/transform.cjs +++ b/src/transformers/addMigrationComments/transform.cjs @@ -1,3 +1,5 @@ +const Sentry = require('@sentry/node'); + const { wrapJscodeshift } = require('../../utils/dom.cjs'); const { addTodoComment } = require('../../utils/jscodeshift.cjs'); @@ -90,6 +92,11 @@ module.exports = function (fileInfo, api) { } }); + if (hasChanges) { + Sentry.setTag('added-todo-comments', true); + Sentry.metrics.increment('added-todo-comments', 1); + } + return hasChanges ? tree.toSource() : undefined; }); }; diff --git a/src/utils/clackUtils.js b/src/utils/clackUtils.js index 80e99e4..abd57ad 100644 --- a/src/utils/clackUtils.js +++ b/src/utils/clackUtils.js @@ -21,11 +21,8 @@ import { traceStep } from './telemetry.js'; export async function abortIfCancelled(input) { if (isCancel(await input)) { cancel('Migr8 run cancelled, see you next time :)'); - const sentryHub = Sentry.getCurrentHub(); - const sentryTransaction = sentryHub.getScope().getTransaction(); - sentryTransaction?.setStatus('cancelled'); - sentryTransaction?.finish(); - sentryHub.captureSession(true); + Sentry.getActiveSpan()?.setStatus('ok'); + Sentry.getActiveSpan()?.end(); await Sentry.flush(3000); process.exit(0); } else { @@ -145,15 +142,13 @@ function hasChangedFiles() { */ async function abort(customMessage, exitCode = 0) { cancel(customMessage ?? 'Exiting, see you next time :)'); - const sentryHub = Sentry.getCurrentHub(); - const sentryTransaction = sentryHub.getScope().getTransaction(); - sentryTransaction?.setStatus('aborted'); - sentryTransaction?.finish(); - const sentrySession = sentryHub.getScope().getSession(); + Sentry.getActiveSpan()?.setStatus('ok'); + const sentrySession = Sentry.getCurrentScope().getSession(); if (sentrySession) { - sentrySession.status = exitCode === 0 ? 'abnormal' : 'crashed'; - sentryHub.captureSession(true); + sentrySession.status = 'abnormal'; } + Sentry.endSession(); + await Sentry.flush(3000); process.exit(exitCode); } diff --git a/src/utils/telemetry.js b/src/utils/telemetry.js index 6de3e76..49f68d1 100644 --- a/src/utils/telemetry.js +++ b/src/utils/telemetry.js @@ -1,15 +1,6 @@ import fs from 'fs'; -import { - defaultStackParser, - Hub, - Integrations, - makeMain, - makeNodeTransport, - NodeClient, - runWithAsyncContext, - trace, -} from '@sentry/node'; +import * as Sentry from '@sentry/node'; const packageJson = JSON.parse(fs.readFileSync(new URL('../../package.json', import.meta.url)).toString()); @@ -18,54 +9,24 @@ const packageJson = JSON.parse(fs.readFileSync(new URL('../../package.json', imp * @template F * @param {{enabled: boolean}} options * @param {() => F | Promise} callback - * @returns {Promise} + * @returns {Promise} */ export async function withTelemetry(options, callback) { - const { sentryHub, sentryClient } = createSentryInstance(options.enabled); - - makeMain(sentryHub); - - const transaction = sentryHub.startTransaction({ - name: 'sentry-migr8-execution', - status: 'ok', - op: 'migr8.flow', - }); - sentryHub.getScope().setSpan(transaction); - const sentrySession = sentryHub.startSession(); - sentryHub.captureSession(); - - try { - return await runWithAsyncContext(() => callback()); - } catch (e) { - sentryHub.captureException('Error during migr8 execution.'); - transaction.setStatus('internal_error'); - sentrySession.status = 'crashed'; - throw e; - } finally { - transaction.finish(); - sentryHub.endSession(); - await sentryClient.flush(3000); - } -} - -/** - * Creates a minimal Sentry instance - * @param {boolean} enabled - * @returns {{sentryHub: Hub, sentryClient: NodeClient}} - */ -function createSentryInstance(enabled) { - const client = new NodeClient({ + Sentry.init({ dsn: 'https://213a6b07baab39624aa94dd08917c5c6@o1.ingest.sentry.io/4505760680837120', - enabled, + enabled: options.enabled, tracesSampleRate: 1, sampleRate: 1, release: packageJson.version, - integrations: [new Integrations.Http()], + + defaultIntegrations: false, + integrations: [Sentry.httpIntegration()], + tracePropagationTargets: [/^https:\/\/sentry.io\//], - stackParser: defaultStackParser, + stackParser: Sentry.defaultStackParser, beforeSendTransaction: event => { delete event.server_name; // Server name might contain PII @@ -81,17 +42,34 @@ function createSentryInstance(enabled) { return event; }, - transport: makeNodeTransport, + transport: Sentry.makeNodeTransport, - debug: true, + debug: false, }); - const hub = new Hub(client); - - hub.setTag('node', process.version); - hub.setTag('platform', process.platform); - - return { sentryHub: hub, sentryClient: client }; + await Sentry.startSpan( + { + name: 'sentry-migr8-execution', + op: 'migr8.flow', + }, + async span => { + const sentrySession = Sentry.startSession(); + Sentry.captureSession(); + + try { + return await Sentry.withScope(() => callback()); + } catch (e) { + Sentry.captureException('Error during migr8 execution.'); + span?.setStatus('error'); + sentrySession.status = 'crashed'; + throw e; + } finally { + span?.end(); + Sentry.endSession(); + await Sentry.flush(3000); + } + } + ); } /** @@ -102,5 +80,5 @@ function createSentryInstance(enabled) { * @returns {Promise} */ export async function traceStep(step, callback) { - return trace({ name: step, op: 'mgir8.step' }, () => callback()); + return Sentry.startSpan({ name: step, op: 'mgir8.step' }, () => callback()); }