From 16e22bb73034f30f22104293347198f9a509a1c5 Mon Sep 17 00:00:00 2001 From: John Hobbs Date: Tue, 1 Oct 2024 16:19:25 -0500 Subject: [PATCH] Use compile time environment variables for Sentry The dev/prod division belongs in `environment`, we will bake the cli/action distinction as `entrypoint` --- node-src/errorMonitoring.test.ts | 47 ++++++++++++++++++++++++++++++ node-src/errorMonitoring.ts | 49 +++++++++++++++++++++++++++++++- package.json | 2 +- tsup.config.ts | 6 ++++ yarn.lock | 15 ++-------- 5 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 node-src/errorMonitoring.test.ts diff --git a/node-src/errorMonitoring.test.ts b/node-src/errorMonitoring.test.ts new file mode 100644 index 000000000..e19d30e03 --- /dev/null +++ b/node-src/errorMonitoring.test.ts @@ -0,0 +1,47 @@ +import * as Sentry from '@sentry/node'; +import chalk from 'chalk'; +import { describe, expect, it } from 'vitest'; + +import { filterBreadcrumb, filterErrorEvent } from './errorMonitoring'; + +const redError = chalk.red('error'); +const blueMessage = chalk.blue('message'); + +describe('filterErrorEvent', () => { + it('removes ANSI from error.message', () => { + const actual = filterErrorEvent({ message: redError } as Sentry.ErrorEvent); + expect(actual.message).toBe('error'); + }); + + it('remove ANSI from exceptions', () => { + const actual = filterErrorEvent({ + exception: { values: [{ value: redError }, { value: redError }] }, + } as Sentry.ErrorEvent); + expect(actual.exception.values[0].value).toBe('error'); + expect(actual.exception.values[1].value).toBe('error'); + }); +}); + +describe('filterBreadcrumb', () => { + it('does nothing with non-console breadcrumbs', () => { + const breadcrumb = { category: 'http', message: blueMessage } as Sentry.Breadcrumb; + const actual = filterBreadcrumb(breadcrumb); + expect(actual).toBe(breadcrumb); + }); + + it('removes ANSI from console breadcrumb messages', () => { + const actual = filterBreadcrumb({ + category: 'console', + message: blueMessage, + } as Sentry.Breadcrumb); + expect(actual.message).toBe('message'); + }); + + it('returns null for empty console breadcrumbs', () => { + const actual = filterBreadcrumb({ + category: 'console', + message: '', + } as Sentry.Breadcrumb); + expect(actual).toBeNull(); + }); +}); diff --git a/node-src/errorMonitoring.ts b/node-src/errorMonitoring.ts index f41445579..a926fe47b 100644 --- a/node-src/errorMonitoring.ts +++ b/node-src/errorMonitoring.ts @@ -1,15 +1,62 @@ import * as Sentry from '@sentry/node'; +import stripAnsi from 'strip-ansi'; + +/** + * Remove ANSI escape codes from Sentry ErrorEvents. + * + * @param event An event containing a captured exception. + * + * @returns The modified event. + */ +export function filterErrorEvent(event: Sentry.ErrorEvent) { + // Remove ANSI escape codes from error messages + if (event?.message) { + event.message = stripAnsi(event.message); + } + // And from exception messages + if (event?.exception?.values) { + for (const [index, exception] of event.exception.values.entries()) { + event.exception.values[index].value = stripAnsi(exception.value); + } + } + return event; +} + +/** + * Remove ANSI escape codes from console breadcrumbs, and skip breadcrumbs for empty lines. + * + * @param breadcrumb A breadcrumb captured by Sentry. + * + * @returns The modified breadcrumb, or `null` when it is not desired. + */ +export function filterBreadcrumb(breadcrumb: Sentry.Breadcrumb) { + if (breadcrumb?.category === 'console') { + // Do not send breadcrumbs for console newlines + if (breadcrumb.message === '') { + // eslint-disable-next-line unicorn/no-null + return null; + } + // Otherwise remove ANSI escape codes + if (breadcrumb?.message) { + breadcrumb.message = stripAnsi(breadcrumb.message); + } + } + return breadcrumb; +} Sentry.init({ dsn: 'https://4fa173db2ef3fb073b8ea153a5466d28@o4504181686599680.ingest.us.sentry.io/4507930289373184', sampleRate: 1, - environment: process.env.CI && process.env.GITHUB_RUN_ID ? 'action' : 'cli', + environment: process.env.SENTRY_ENVIRONMENT, enabled: process.env.DISABLE_ERROR_MONITORING !== 'true', enableTracing: false, integrations: [], initialScope: { tags: { + entrypoint: process.env.CI && process.env.GITHUB_RUN_ID ? 'action' : 'cli', version: process.env.npm_package_version, }, }, + beforeSend: filterErrorEvent, + beforeBreadcrumb: filterBreadcrumb, }); diff --git a/package.json b/package.json index 91b7a168a..b1cbf2095 100644 --- a/package.json +++ b/package.json @@ -190,7 +190,7 @@ "sort-package-json": "1.50.0", "storybook": "^8.1.5", "string-argv": "^0.3.1", - "strip-ansi": "6.0.0", + "strip-ansi": "^7.1.0", "tmp-promise": "3.0.2", "ts-dedent": "^1.0.0", "ts-loader": "^9.2.5", diff --git a/tsup.config.ts b/tsup.config.ts index 6facb596d..fa5788fbe 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -18,6 +18,9 @@ export default defineConfig((options) => [ clean: true, platform: 'node', target: 'node16', // Storybook still supports Node 16 + env: { + SENTRY_ENVIRONMENT: process.env.CI ? 'production' : 'development', + }, }, { entry: ['action-src/register.js'], @@ -30,5 +33,8 @@ export default defineConfig((options) => [ clean: true, platform: 'node', target: 'node20', // Sync with `runs.using` in action.yml + env: { + SENTRY_ENVIRONMENT: process.env.CI ? 'production' : 'development', + }, }, ]); diff --git a/yarn.lock b/yarn.lock index 94dd1ccce..4ccb90070 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6289,7 +6289,7 @@ __metadata: languageName: node linkType: hard -"ansi-regex@npm:^5.0.0, ansi-regex@npm:^5.0.1": +"ansi-regex@npm:^5.0.1": version: 5.0.1 resolution: "ansi-regex@npm:5.0.1" checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 @@ -7516,7 +7516,7 @@ __metadata: sort-package-json: "npm:1.50.0" storybook: "npm:^8.1.5" string-argv: "npm:^0.3.1" - strip-ansi: "npm:6.0.0" + strip-ansi: "npm:^7.1.0" tmp-promise: "npm:3.0.2" ts-dedent: "npm:^1.0.0" ts-loader: "npm:^9.2.5" @@ -17971,15 +17971,6 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:6.0.0": - version: 6.0.0 - resolution: "strip-ansi@npm:6.0.0" - dependencies: - ansi-regex: "npm:^5.0.0" - checksum: 10c0/85257c80250541cc0e65088c7dc768563bdbd1bf7120471d6d3a73cdc60e8149a50038c12a6fd4a30b674587f306ae42e2cc73ac3095daf193633daa0bd8f928 - languageName: node - linkType: hard - "strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1": version: 3.0.1 resolution: "strip-ansi@npm:3.0.1" @@ -17998,7 +17989,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^7.0.0, strip-ansi@npm:^7.0.1": +"strip-ansi@npm:^7.0.0, strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0": version: 7.1.0 resolution: "strip-ansi@npm:7.1.0" dependencies: