From f8eec3f12412224371dd82283ffbcd71d9ef725a Mon Sep 17 00:00:00 2001 From: dschom Date: Wed, 11 Feb 2026 16:38:42 -0800 Subject: [PATCH] refactor(sentry): Migrate from fxa-shared/sentry to @fxa/sentry-* packages Migrated all Sentry imports from the legacy fxa-shared/sentry module to the new scoped @fxa/sentry-* packages in libs/shared/: - Browser code: @fxa/shared/sentry/browser - Node.js code: @fxa/sentry-node - Shared utilities: @fxa/sentry-utils Changes: - Added shorter TypeScript path aliases (@fxa/sentry-*) in tsconfig.base.json - Updated 25 files across auth-server, content-server, settings, admin-panel, event-broker, and payments-server - Removed tagCriticalEvent usage (deprecated) - Added tracesSampler support to SentryConfigOpts types - Deleted legacy packages/fxa-shared/sentry/ directory and tests - Removed sentry exports from fxa-shared package.json - Ports fxa-shared/monitoring to libs - Updates references to fxa-shared/monitoring This completes the migration to the modular sentry package structure. Co-Authored-By: Claude Sonnet 4.5 --- libs/shared/monitoring/.eslintrc.json | 18 + libs/shared/monitoring/.swcrc | 14 + libs/shared/monitoring/README.md | 34 ++ libs/shared/monitoring/jest.config.ts | 42 ++ libs/shared/monitoring/package.json | 4 + libs/shared/monitoring/project.json | 51 +++ libs/shared/monitoring/src/index.spec.ts | 63 +++ .../shared/monitoring/src}/index.ts | 10 +- libs/shared/monitoring/tsconfig.json | 16 + libs/shared/monitoring/tsconfig.lib.json | 11 + libs/shared/monitoring/tsconfig.spec.json | 14 + libs/shared/sentry-node/src/index.ts | 1 + .../src/lib/models/sentry-config-opts.ts | 3 + .../sentry/src/lib/models/SentryConfigOpts.ts | 3 + .../fxa-admin-panel/server/lib/monitoring.ts | 2 +- packages/fxa-admin-panel/src/index.tsx | 2 +- packages/fxa-auth-server/lib/monitoring.js | 2 +- .../fxa-auth-server/lib/payments/stripe.ts | 17 +- packages/fxa-auth-server/lib/sentry.js | 2 +- packages/fxa-auth-server/lib/server.js | 4 +- .../scripts/paypal-processor.ts | 7 +- packages/fxa-auth-server/test/local/server.js | 2 +- .../app/scripts/lib/sentry.js | 7 +- .../server/lib/routes/get-update-firefox.js | 4 +- .../server/lib/routes/post-csp.js | 4 +- .../server/lib/routes/post-metrics.js | 4 +- .../lib/routes/post-nimbus-experiments.js | 9 +- .../lib/routes/redirect-download-firefox.js | 4 +- .../fxa-content-server/server/lib/sentry.js | 2 +- packages/fxa-customs-server/lib/monitoring.js | 2 +- packages/fxa-event-broker/src/monitoring.ts | 27 +- .../server/lib/monitoring.js | 2 +- .../fxa-payments-server/src/lib/sentry.js | 4 +- packages/fxa-profile-server/lib/monitoring.js | 2 +- .../src/components/App/index.test.tsx | 4 +- .../fxa-settings/src/components/App/index.tsx | 5 +- packages/fxa-settings/src/index.tsx | 2 +- packages/fxa-settings/src/lib/metrics.ts | 2 +- .../ConfirmSignupCode/container.test.tsx | 9 +- packages/fxa-shared/package.json | 12 - packages/fxa-shared/sentry/browser.ts | 136 ------- packages/fxa-shared/sentry/config-builder.ts | 88 ---- packages/fxa-shared/sentry/index.ts | 3 - .../sentry/joi-message-overrides.ts | 40 -- .../sentry/models/SentryConfigOpts.ts | 35 -- packages/fxa-shared/sentry/models/pii.ts | 41 -- packages/fxa-shared/sentry/node.ts | 65 --- .../fxa-shared/sentry/pii-filter-actions.ts | 333 --------------- packages/fxa-shared/sentry/pii-filters.ts | 81 ---- .../sentry/report-validation-error.ts | 56 --- packages/fxa-shared/sentry/tag.ts | 107 ----- packages/fxa-shared/test/sentry/browser.ts | 112 ----- .../fxa-shared/test/sentry/config-builder.ts | 136 ------- .../fxa-shared/test/sentry/event-tagging.ts | 53 --- .../test/sentry/joi-message-overrides.ts | 26 -- .../test/sentry/pii-filter-actions.ts | 381 ------------------ .../fxa-shared/test/sentry/pii-filters.ts | 69 ---- packages/fxa-shared/tracing/pii-filters.ts | 6 +- tsconfig.base.json | 6 + 59 files changed, 362 insertions(+), 1839 deletions(-) create mode 100644 libs/shared/monitoring/.eslintrc.json create mode 100644 libs/shared/monitoring/.swcrc create mode 100644 libs/shared/monitoring/README.md create mode 100644 libs/shared/monitoring/jest.config.ts create mode 100644 libs/shared/monitoring/package.json create mode 100644 libs/shared/monitoring/project.json create mode 100644 libs/shared/monitoring/src/index.spec.ts rename {packages/fxa-shared/monitoring => libs/shared/monitoring/src}/index.ts (80%) create mode 100644 libs/shared/monitoring/tsconfig.json create mode 100644 libs/shared/monitoring/tsconfig.lib.json create mode 100644 libs/shared/monitoring/tsconfig.spec.json delete mode 100644 packages/fxa-shared/sentry/browser.ts delete mode 100644 packages/fxa-shared/sentry/config-builder.ts delete mode 100644 packages/fxa-shared/sentry/index.ts delete mode 100644 packages/fxa-shared/sentry/joi-message-overrides.ts delete mode 100644 packages/fxa-shared/sentry/models/SentryConfigOpts.ts delete mode 100644 packages/fxa-shared/sentry/models/pii.ts delete mode 100644 packages/fxa-shared/sentry/node.ts delete mode 100644 packages/fxa-shared/sentry/pii-filter-actions.ts delete mode 100644 packages/fxa-shared/sentry/pii-filters.ts delete mode 100644 packages/fxa-shared/sentry/report-validation-error.ts delete mode 100644 packages/fxa-shared/sentry/tag.ts delete mode 100644 packages/fxa-shared/test/sentry/browser.ts delete mode 100644 packages/fxa-shared/test/sentry/config-builder.ts delete mode 100644 packages/fxa-shared/test/sentry/event-tagging.ts delete mode 100644 packages/fxa-shared/test/sentry/joi-message-overrides.ts delete mode 100644 packages/fxa-shared/test/sentry/pii-filter-actions.ts delete mode 100644 packages/fxa-shared/test/sentry/pii-filters.ts diff --git a/libs/shared/monitoring/.eslintrc.json b/libs/shared/monitoring/.eslintrc.json new file mode 100644 index 00000000000..3456be9b903 --- /dev/null +++ b/libs/shared/monitoring/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/shared/monitoring/.swcrc b/libs/shared/monitoring/.swcrc new file mode 100644 index 00000000000..f52b4e44979 --- /dev/null +++ b/libs/shared/monitoring/.swcrc @@ -0,0 +1,14 @@ +{ + "jsc": { + "target": "es2017", + "parser": { + "syntax": "typescript", + "decorators": true, + "dynamicImport": true + }, + "transform": { + "decoratorMetadata": true, + "legacyDecorator": true + } + } +} diff --git a/libs/shared/monitoring/README.md b/libs/shared/monitoring/README.md new file mode 100644 index 00000000000..74d15eea7a5 --- /dev/null +++ b/libs/shared/monitoring/README.md @@ -0,0 +1,34 @@ +# shared-monitoring + +This library provides monitoring functionality including error tracking with Sentry and distributed tracing. + +It exports the `initMonitoring` function which initializes both error monitoring (Sentry) and performance monitoring (tracing) for server applications. + +## Installation + +```bash +npm install @fxa/shared/monitoring +``` + +## Usage + +```typescript +import { initMonitoring } from '@fxa/shared/monitoring'; + +initMonitoring({ + log: logger, + config: { + tracing: { + /* tracing config */ + }, + sentry: { + /* sentry config */ + }, + }, +}); +``` + +## Exported APIs + +- `initMonitoring(opts: MonitoringConfig)` - Initialize monitoring components +- `MonitoringConfig` - Type definition for monitoring configuration diff --git a/libs/shared/monitoring/jest.config.ts b/libs/shared/monitoring/jest.config.ts new file mode 100644 index 00000000000..0dd7c87df86 --- /dev/null +++ b/libs/shared/monitoring/jest.config.ts @@ -0,0 +1,42 @@ +import { Config } from 'jest'; +/* eslint-disable */ +import { readFileSync } from 'fs'; + +// Reading the SWC compilation config and remove the "exclude" +// for the test files to be compiled by SWC +const { exclude: _, ...swcJestConfig } = JSON.parse( + readFileSync(`${__dirname}/.swcrc`, 'utf-8') +); + +// disable .swcrc look-up by SWC core because we're passing in swcJestConfig ourselves. +// If we do not disable this, SWC Core will read .swcrc and won't transform our test files due to "exclude" +if (swcJestConfig.swcrc === undefined) { + swcJestConfig.swcrc = false; +} + +// Uncomment if using global setup/teardown files being transformed via swc +// https://nx.dev/packages/jest/documents/overview#global-setup/teardown-with-nx-libraries +// jest needs EsModule Interop to find the default exported setup/teardown functions +// swcJestConfig.module.noInterop = false; + +const config: Config = { + displayName: 'shared-monitoring', + preset: '../../../jest.preset.js', + transform: { + '^.+\\.[tj]s$': ['@swc/jest', swcJestConfig], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + testEnvironment: 'node', + coverageDirectory: '../../../coverage/libs/shared/monitoring', + reporters: [ + 'default', + [ + 'jest-junit', + { + outputDirectory: 'artifacts/tests/shared-monitoring', + outputName: 'shared-monitoring-jest-unit-results.xml', + }, + ], + ], +}; +export default config; diff --git a/libs/shared/monitoring/package.json b/libs/shared/monitoring/package.json new file mode 100644 index 00000000000..13e95fd4c0a --- /dev/null +++ b/libs/shared/monitoring/package.json @@ -0,0 +1,4 @@ +{ + "name": "@fxa/shared/monitoring", + "version": "0.0.0" +} diff --git a/libs/shared/monitoring/project.json b/libs/shared/monitoring/project.json new file mode 100644 index 00000000000..fd8dbe6183d --- /dev/null +++ b/libs/shared/monitoring/project.json @@ -0,0 +1,51 @@ +{ + "name": "shared-monitoring", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/shared/monitoring/src", + "projectType": "library", + "tags": ["scope:shared:lib"], + "targets": { + "build": { + "executor": "@nx/esbuild:esbuild", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "main": "libs/shared/monitoring/src/index.ts", + "outputPath": "dist/libs/shared/monitoring", + "outputFileName": "main.js", + "tsConfig": "libs/shared/monitoring/tsconfig.lib.json", + "declaration": true, + "assets": [ + { + "glob": "libs/shared/monitoring/README.md", + "input": ".", + "output": "." + } + ], + "platform": "node" + }, + "configurations": { + "development": { + "minify": false + }, + "production": { + "minify": true + } + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/shared/monitoring/**/*.ts"] + } + }, + "test-unit": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/shared/monitoring/jest.config.ts" + } + } + } +} diff --git a/libs/shared/monitoring/src/index.spec.ts b/libs/shared/monitoring/src/index.spec.ts new file mode 100644 index 00000000000..66ed2e984c8 --- /dev/null +++ b/libs/shared/monitoring/src/index.spec.ts @@ -0,0 +1,63 @@ +import { initTracing } from '@fxa/shared/otel'; +import { initSentry } from '@fxa/sentry-node'; +import { initMonitoring } from '@fxa/shared/monitoring'; + +jest.mock('@fxa/shared/otel', () => ({ initTracing: jest.fn() })); +jest.mock('@fxa/sentry-node', () => ({ initSentry: jest.fn() })); + +describe('shared-monitoring', () => { + beforeEach(() => { + jest.resetAllMocks(); + jest.resetModules(); + }); + + it('initializes tracing and sentry when config contains them', () => { + const log = { + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + }; + const opts = { + log, + config: { + sentry: { dsn: 'https://example.com' }, + tracing: { enabled: true, serviceName: 'test' }, + }, + }; + + initMonitoring(opts); + + expect(initTracing).toHaveBeenCalledWith(opts.config.tracing, log); + expect(initSentry).toHaveBeenCalledWith(opts.config, log); + }); + + it('does not call tracing or sentry when not configured', () => { + const log = { warn: jest.fn() }; + const opts = { log, config: {} }; + + initMonitoring(opts); + + expect(initTracing).not.toHaveBeenCalled(); + expect(initSentry).not.toHaveBeenCalled(); + }); + + it('warns and skips when initialized more than once', () => { + const log = { warn: jest.fn() }; + const opts = { + log, + config: { + sentry: { dsn: 'https://example.com' }, + tracing: { enabled: true, serviceName: 'test' }, + }, + }; + + initMonitoring(opts); + initMonitoring(opts); + + expect(log.warn).toHaveBeenCalledWith( + 'monitoring', + 'Monitoring can only be initialized once' + ); + }); +}); diff --git a/packages/fxa-shared/monitoring/index.ts b/libs/shared/monitoring/src/index.ts similarity index 80% rename from packages/fxa-shared/monitoring/index.ts rename to libs/shared/monitoring/src/index.ts index 56e0c9d76d7..16a36cf1f4b 100644 --- a/packages/fxa-shared/monitoring/index.ts +++ b/libs/shared/monitoring/src/index.ts @@ -2,10 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { initTracing } from '../tracing/node-tracing'; -import { InitSentryOpts, initSentry } from '../sentry/node'; -import { TracingOpts } from '../tracing/config'; -import { ILogger } from '../log'; +import { initTracing } from '@fxa/shared/otel'; +import { InitSentryOpts, initSentry } from '@fxa/sentry-node'; +import { TracingOpts } from '@fxa/shared/otel'; +import { ILogger } from '@fxa/shared/log'; export type MonitoringConfig = { log?: ILogger; @@ -14,7 +14,7 @@ export type MonitoringConfig = { let initialized = false; -// IMPORTANT! This initialization function must be called first thing when a server starts.If it's called after server +// IMPORTANT! This initialization function must be called first thing when a server starts. If it's called after server // frameworks initialized instrumentation might not work properly. /** * Initializes modules related to error monitoring, performance monitoring, and tracing. diff --git a/libs/shared/monitoring/tsconfig.json b/libs/shared/monitoring/tsconfig.json new file mode 100644 index 00000000000..25f7201d870 --- /dev/null +++ b/libs/shared/monitoring/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs" + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/shared/monitoring/tsconfig.lib.json b/libs/shared/monitoring/tsconfig.lib.json new file mode 100644 index 00000000000..e583571eac8 --- /dev/null +++ b/libs/shared/monitoring/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/libs/shared/monitoring/tsconfig.spec.json b/libs/shared/monitoring/tsconfig.spec.json new file mode 100644 index 00000000000..69a251f328c --- /dev/null +++ b/libs/shared/monitoring/tsconfig.spec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/libs/shared/sentry-node/src/index.ts b/libs/shared/sentry-node/src/index.ts index 595d792b30f..3f29ee4c26a 100644 --- a/libs/shared/sentry-node/src/index.ts +++ b/libs/shared/sentry-node/src/index.ts @@ -1,3 +1,4 @@ export * from './lib/joi-message-overrides'; export * from './lib/node'; export * from './lib/report-validation-error'; +export type { InitSentryOpts, Logger } from '@fxa/shared/sentry-utils'; diff --git a/libs/shared/sentry-utils/src/lib/models/sentry-config-opts.ts b/libs/shared/sentry-utils/src/lib/models/sentry-config-opts.ts index f7fcafca1cc..9cd34d2f5a1 100644 --- a/libs/shared/sentry-utils/src/lib/models/sentry-config-opts.ts +++ b/libs/shared/sentry-utils/src/lib/models/sentry-config-opts.ts @@ -29,6 +29,9 @@ export type SentryConfigOpts = { /** The tracing sample rate. Setting this above 0 will aso result in performance metrics being captured. */ tracesSampleRate?: number; + + /** The tracing sampler function. Allows dynamic sampling based on context. */ + tracesSampler?: (context: any) => number; }; }; diff --git a/libs/shared/sentry/src/lib/models/SentryConfigOpts.ts b/libs/shared/sentry/src/lib/models/SentryConfigOpts.ts index 6f60886b8aa..b382ae1bff9 100644 --- a/libs/shared/sentry/src/lib/models/SentryConfigOpts.ts +++ b/libs/shared/sentry/src/lib/models/SentryConfigOpts.ts @@ -40,6 +40,9 @@ export type SentryConfigOpts = { /** The tracing sample rate. Setting this above 0 will aso result in performance metrics being captured. */ tracesSampleRate?: number; + /** The tracing sampler function. Allows dynamic sampling based on context. */ + tracesSampler?: (context: any) => number; + /** Indicates if PII can be transeferred. e.g. Send the IP address. */ sendDefaultPii?: boolean; }; diff --git a/packages/fxa-admin-panel/server/lib/monitoring.ts b/packages/fxa-admin-panel/server/lib/monitoring.ts index 3322d99ecea..0d8d8ff142f 100644 --- a/packages/fxa-admin-panel/server/lib/monitoring.ts +++ b/packages/fxa-admin-panel/server/lib/monitoring.ts @@ -4,7 +4,7 @@ import Config from '../config'; import mozLog from 'mozlog'; -import { initMonitoring } from 'fxa-shared/monitoring'; +import { initMonitoring } from '@fxa/shared/monitoring'; import { version } from '../../package.json'; const config = Config.getProperties(); diff --git a/packages/fxa-admin-panel/src/index.tsx b/packages/fxa-admin-panel/src/index.tsx index 2b220b63d44..a02568fe0a2 100644 --- a/packages/fxa-admin-panel/src/index.tsx +++ b/packages/fxa-admin-panel/src/index.tsx @@ -9,7 +9,7 @@ import AppErrorBoundary from 'fxa-react/components/AppErrorBoundary'; import AppLocalizationProvider from 'fxa-react/lib/AppLocalizationProvider'; import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client'; import { setContext } from '@apollo/client/link/context'; -import sentryMetrics from 'fxa-shared/sentry/browser'; +import sentryMetrics from '@fxa/shared/sentry/browser'; import { config, readConfigFromMeta, getExtraHeaders } from './lib/config'; import App from './App'; import './styles/tailwind.out.css'; diff --git a/packages/fxa-auth-server/lib/monitoring.js b/packages/fxa-auth-server/lib/monitoring.js index d03dbb7be3b..aa93277f7a8 100644 --- a/packages/fxa-auth-server/lib/monitoring.js +++ b/packages/fxa-auth-server/lib/monitoring.js @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const { initMonitoring } = require('fxa-shared/monitoring'); +const { initMonitoring } = require('@fxa/shared/monitoring'); const Sentry = require('@sentry/node'); const { config } = require('../config'); const logger = require('./log')( diff --git a/packages/fxa-auth-server/lib/payments/stripe.ts b/packages/fxa-auth-server/lib/payments/stripe.ts index 852b78de066..b2caef648b8 100644 --- a/packages/fxa-auth-server/lib/payments/stripe.ts +++ b/packages/fxa-auth-server/lib/payments/stripe.ts @@ -68,7 +68,7 @@ import { subscriptionProductMetadataValidator } from '../routes/validators'; import { formatMetadataValidationErrorMessage, reportValidationError, -} from 'fxa-shared/sentry/report-validation-error'; +} from '@fxa/sentry-node'; import { AppConfig, AuthFirestore, AuthLogger, TaxAddress } from '../types'; import { PaymentConfigManager } from './configuration/manager'; import { CurrencyHelper } from './currencies'; @@ -3502,7 +3502,10 @@ export class StripeHelper extends StripeHelperBase { return; } - return this.stripeFirestore.fetchAndInsertCustomer(customerId, event.created); + return this.stripeFirestore.fetchAndInsertCustomer( + customerId, + event.created + ); } /** @@ -3525,7 +3528,10 @@ export class StripeHelper extends StripeHelperBase { CUSTOMER_RESOURCE ); if (!customer.deleted && !customer.currency) { - await this.stripeFirestore.fetchAndInsertCustomer(customerId, event.created); + await this.stripeFirestore.fetchAndInsertCustomer( + customerId, + event.created + ); const subscription = await this.stripe.subscriptions.retrieve(subscriptionId); return subscription; @@ -3566,7 +3572,10 @@ export class StripeHelper extends StripeHelperBase { ); } catch (err) { if (err.name === FirestoreStripeError.FIRESTORE_CUSTOMER_NOT_FOUND) { - await this.stripeFirestore.fetchAndInsertCustomer(customerId, event.created); + await this.stripeFirestore.fetchAndInsertCustomer( + customerId, + event.created + ); await this.stripeFirestore.fetchAndInsertInvoice( invoiceId, event.created diff --git a/packages/fxa-auth-server/lib/sentry.js b/packages/fxa-auth-server/lib/sentry.js index 87d444f321f..1461d23c1a8 100644 --- a/packages/fxa-auth-server/lib/sentry.js +++ b/packages/fxa-auth-server/lib/sentry.js @@ -12,7 +12,7 @@ const { ignoreErrors } = require('@fxa/accounts/errors'); const { formatMetadataValidationErrorMessage, reportValidationError, -} = require('fxa-shared/sentry/report-validation-error'); +} = require('@fxa/sentry-node'); function reportSentryMessage(message, captureContext) { Sentry.withScope((scope) => { diff --git a/packages/fxa-auth-server/lib/server.js b/packages/fxa-auth-server/lib/server.js index f9eb3db0ce3..39c1c686ec4 100644 --- a/packages/fxa-auth-server/lib/server.js +++ b/packages/fxa-auth-server/lib/server.js @@ -20,9 +20,7 @@ const { configureSentry } = require('./sentry'); const { swaggerOptions } = require('../docs/swagger/swagger-options'); const { Account } = require('fxa-shared/db/models/auth'); const { determineLocale } = require('../../../libs/shared/l10n/src'); -const { - reportValidationError, -} = require('fxa-shared/sentry/report-validation-error'); +const { reportValidationError } = require('@fxa/sentry-node'); const { logErrorWithGlean } = require('./metrics/glean'); const mfa = require('./routes/auth-schemes/mfa'); const verifiedSessionToken = require('./routes/auth-schemes/verified-session-token'); diff --git a/packages/fxa-auth-server/scripts/paypal-processor.ts b/packages/fxa-auth-server/scripts/paypal-processor.ts index 5347db5d97d..a6eb16d1f2f 100644 --- a/packages/fxa-auth-server/scripts/paypal-processor.ts +++ b/packages/fxa-auth-server/scripts/paypal-processor.ts @@ -13,7 +13,7 @@ import { PayPalHelper } from '../lib/payments/paypal/helper'; import { PayPalClient } from '@fxa/payments/paypal'; import { PaypalProcessor } from '../lib/payments/paypal/processor'; import { setupProcessingTaskObjects } from '../lib/payments/processing-tasks-setup'; -import { initSentry } from 'packages/fxa-shared/sentry/node'; +import { initSentry } from '@fxa/sentry-node'; const pckg = require('../package.json'); const config = require('../config').default.getProperties(); @@ -62,9 +62,8 @@ export async function init() { const lockDuration = parseInt(`${program.lockDuration}`) || DEFAULT_LOCK_DURATION_MS; - const { log, database, senders } = await setupProcessingTaskObjects( - 'paypal-processor' - ); + const { log, database, senders } = + await setupProcessingTaskObjects('paypal-processor'); initSentry( { diff --git a/packages/fxa-auth-server/test/local/server.js b/packages/fxa-auth-server/test/local/server.js index 01869a4023e..29e8e86a4e9 100644 --- a/packages/fxa-auth-server/test/local/server.js +++ b/packages/fxa-auth-server/test/local/server.js @@ -20,7 +20,7 @@ const customs = mocks.mockCustoms(); const sandbox = sinon.createSandbox(); const mockReportValidationError = sandbox.stub(); const server = proxyquire(`${ROOT_DIR}/lib/server`, { - 'fxa-shared/sentry/report-validation-error': { + '@fxa/sentry-node': { reportValidationError: mockReportValidationError, }, }); diff --git a/packages/fxa-content-server/app/scripts/lib/sentry.js b/packages/fxa-content-server/app/scripts/lib/sentry.js index 310b00fd33f..84ac5d69182 100644 --- a/packages/fxa-content-server/app/scripts/lib/sentry.js +++ b/packages/fxa-content-server/app/scripts/lib/sentry.js @@ -3,11 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import * as Sentry from '@sentry/browser'; -import { - buildSentryConfig, - tagCriticalEvent, - tagFxaName, -} from 'fxa-shared/sentry'; +import { buildSentryConfig, tagFxaName } from '@fxa/sentry-utils'; import _ from 'underscore'; import Logger from './logger'; @@ -22,7 +18,6 @@ import Logger from './logger'; * @private */ function beforeSend(data) { - data = tagCriticalEvent(data); if (data && data.request) { if (data.tags) { const errno = data.tags.errno; diff --git a/packages/fxa-content-server/server/lib/routes/get-update-firefox.js b/packages/fxa-content-server/server/lib/routes/get-update-firefox.js index 65f9a7f92b0..7fd4412f6cd 100644 --- a/packages/fxa-content-server/server/lib/routes/get-update-firefox.js +++ b/packages/fxa-content-server/server/lib/routes/get-update-firefox.js @@ -11,9 +11,7 @@ const { logFlowEvent } = require('../flow-event'); const Url = require('url'); const uuid = require('node-uuid'); const validation = require('../validation'); -const { - overrideJoiMessages, -} = require('fxa-shared/sentry/joi-message-overrides'); +const { overrideJoiMessages } = require('@fxa/sentry-node'); const { ACTION: ACTION_TYPE, diff --git a/packages/fxa-content-server/server/lib/routes/post-csp.js b/packages/fxa-content-server/server/lib/routes/post-csp.js index 0b09be849c8..80d4952123b 100644 --- a/packages/fxa-content-server/server/lib/routes/post-csp.js +++ b/packages/fxa-content-server/server/lib/routes/post-csp.js @@ -12,9 +12,7 @@ const joi = require('joi'); const logger = require('../logging/log')(); const url = require('url'); const validation = require('../validation'); -const { - overrideJoiMessages, -} = require('fxa-shared/sentry/joi-message-overrides'); +const { overrideJoiMessages } = require('@fxa/sentry-node'); const INTEGER_TYPE = validation.TYPES.INTEGER; const STRING_TYPE = validation.TYPES.LONG_STRING; diff --git a/packages/fxa-content-server/server/lib/routes/post-metrics.js b/packages/fxa-content-server/server/lib/routes/post-metrics.js index 314451b9bed..bf84caa15ad 100644 --- a/packages/fxa-content-server/server/lib/routes/post-metrics.js +++ b/packages/fxa-content-server/server/lib/routes/post-metrics.js @@ -12,9 +12,7 @@ const joi = require('joi'); const logger = require('../logging/log')('server.post-metrics'); const MetricsCollector = require('../metrics-collector-stderr'); const validation = require('../validation'); -const { - overrideJoiMessages, -} = require('fxa-shared/sentry/joi-message-overrides'); +const { overrideJoiMessages } = require('@fxa/sentry-node'); const clientMetricsConfig = config.get('client_metrics'); const DISABLE_CLIENT_METRICS_STDERR = diff --git a/packages/fxa-content-server/server/lib/routes/post-nimbus-experiments.js b/packages/fxa-content-server/server/lib/routes/post-nimbus-experiments.js index fb9e3b7e29f..234581baf4f 100644 --- a/packages/fxa-content-server/server/lib/routes/post-nimbus-experiments.js +++ b/packages/fxa-content-server/server/lib/routes/post-nimbus-experiments.js @@ -4,9 +4,7 @@ const joi = require('joi'); const Sentry = require('@sentry/node'); -const { - overrideJoiMessages, -} = require('fxa-shared/sentry/joi-message-overrides'); +const { overrideJoiMessages } = require('@fxa/sentry-node'); const BODY_SCHEMA = { client_id: joi.string().required(), @@ -60,7 +58,8 @@ module.exports = function (config, statsd) { statsd.increment('cirrus.experiment-fetch-success'); } } catch (err) { - const isTimeout = err.name === 'AbortError' || err.name === 'TimeoutError'; + const isTimeout = + err.name === 'AbortError' || err.name === 'TimeoutError'; if (statsd) { statsd.increment('cirrus.experiment-fetch-error'); @@ -74,7 +73,7 @@ module.exports = function (config, statsd) { Sentry.captureException(err, { tags: { source: 'nimbus-experiments', - } + }, }); } diff --git a/packages/fxa-content-server/server/lib/routes/redirect-download-firefox.js b/packages/fxa-content-server/server/lib/routes/redirect-download-firefox.js index e1e70461891..b2f686ba028 100644 --- a/packages/fxa-content-server/server/lib/routes/redirect-download-firefox.js +++ b/packages/fxa-content-server/server/lib/routes/redirect-download-firefox.js @@ -14,9 +14,7 @@ const amplitude = require('../amplitude'); const { logFlowEvent } = require('../flow-event'); const validation = require('../validation'); -const { - overrideJoiMessages, -} = require('fxa-shared/sentry/joi-message-overrides'); +const { overrideJoiMessages } = require('@fxa/sentry-node'); const { ACTION: ACTION_TYPE, diff --git a/packages/fxa-content-server/server/lib/sentry.js b/packages/fxa-content-server/server/lib/sentry.js index a7b33734c0e..b7ab73142cd 100644 --- a/packages/fxa-content-server/server/lib/sentry.js +++ b/packages/fxa-content-server/server/lib/sentry.js @@ -5,7 +5,7 @@ const Sentry = require('@sentry/node'); const config = require('./configuration'); const RELEASE = require('../../package.json').version; -const { buildSentryConfig, tagFxaName } = require('fxa-shared/sentry'); +const { buildSentryConfig, tagFxaName } = require('@fxa/sentry-utils'); const logger = require('./logging/log')('sentry'); if (config.get('sentry.dsn')) { diff --git a/packages/fxa-customs-server/lib/monitoring.js b/packages/fxa-customs-server/lib/monitoring.js index 6f55ba00190..5b2676feb36 100644 --- a/packages/fxa-customs-server/lib/monitoring.js +++ b/packages/fxa-customs-server/lib/monitoring.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const Sentry = require('@sentry/node'); -const { initMonitoring } = require('fxa-shared/monitoring'); +const { initMonitoring } = require('@fxa/shared/monitoring'); const config = require('./config').getProperties(); const log = require('./log')(config.log.level, 'configure-sentry'); const { version } = require('../package.json'); diff --git a/packages/fxa-event-broker/src/monitoring.ts b/packages/fxa-event-broker/src/monitoring.ts index d1349cc69c7..68a9e6db09d 100644 --- a/packages/fxa-event-broker/src/monitoring.ts +++ b/packages/fxa-event-broker/src/monitoring.ts @@ -5,9 +5,32 @@ import Config, { AppConfig } from './config'; import mozLog from 'mozlog'; import { initTracing } from 'fxa-shared/tracing/node-tracing'; -import { initSentry } from 'fxa-shared/sentry/node'; +import { initSentry } from '@fxa/sentry-node'; const log = mozLog(Config.getProperties().log)(Config.getProperties().log.app); const appConfig = Config.getProperties() as AppConfig; initTracing(appConfig.tracing, log); -initSentry(appConfig, log); +initSentry(appConfig, { + error(type: string, data: unknown) { + log.error(type, toObject(data)); + }, + debug(type: string, data: unknown) { + log.debug(type, toObject(data)); + }, + info(type: string, data: unknown) { + log.info(type, toObject(data)); + }, + warn(type: string, data: unknown) { + log.warn(type, toObject(data)); + }, +}); + +function toObject(data: unknown): object { + if (data === null) { + return {}; + } + if (typeof data === 'object') { + return data; + } + return { data }; +} diff --git a/packages/fxa-payments-server/server/lib/monitoring.js b/packages/fxa-payments-server/server/lib/monitoring.js index c71333f843d..4967533e28c 100644 --- a/packages/fxa-payments-server/server/lib/monitoring.js +++ b/packages/fxa-payments-server/server/lib/monitoring.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const Sentry = require('@sentry/node'); -const { initMonitoring } = require('fxa-shared/monitoring'); +const { initMonitoring } = require('@fxa/shared/monitoring'); const { config } = require('../config'); const log = require('./logging/log')('configure-sentry'); const { version } = require('../../package.json'); diff --git a/packages/fxa-payments-server/src/lib/sentry.js b/packages/fxa-payments-server/src/lib/sentry.js index 2fcc8b137c4..b2d3321fbd6 100644 --- a/packages/fxa-payments-server/src/lib/sentry.js +++ b/packages/fxa-payments-server/src/lib/sentry.js @@ -7,9 +7,8 @@ import * as Sentry from '@sentry/browser'; import { tagFxaName, - tagCriticalEvent, buildSentryConfig, -} from 'fxa-shared/sentry'; +} from '@fxa/sentry-utils'; /** * function that gets called before data gets sent to error metrics @@ -79,7 +78,6 @@ SentryMetrics.prototype = { Sentry.init({ ...opts, beforeSend(event) { - event = tagCriticalEvent(event); event = tagFxaName(event, opts.clientName); return event; }, diff --git a/packages/fxa-profile-server/lib/monitoring.js b/packages/fxa-profile-server/lib/monitoring.js index 3ea95c1d3f7..5e87a394840 100644 --- a/packages/fxa-profile-server/lib/monitoring.js +++ b/packages/fxa-profile-server/lib/monitoring.js @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const Sentry = require('@sentry/node'); -const { initMonitoring } = require('fxa-shared/monitoring'); +const { initMonitoring } = require('@fxa/shared/monitoring'); const config = require('./config').getProperties(); const log = require('./logging')('configure-sentry'); const { version } = require('../package.json'); diff --git a/packages/fxa-settings/src/components/App/index.test.tsx b/packages/fxa-settings/src/components/App/index.test.tsx index 727f0fff536..fa32c67ddf8 100644 --- a/packages/fxa-settings/src/components/App/index.test.tsx +++ b/packages/fxa-settings/src/components/App/index.test.tsx @@ -33,7 +33,7 @@ import { currentAccount } from '../../lib/cache'; import { MozServices } from '../../lib/types'; import mockUseFxAStatus from '../../lib/hooks/useFxAStatus/mocks'; import useFxAStatus from '../../lib/hooks/useFxAStatus'; -import sentryMetrics from 'fxa-shared/sentry/browser'; +import sentryMetrics from '@fxa/shared/sentry/browser'; import { OAuthError } from '../../lib/oauth'; jest.mock('../../lib/hooks/useFxAStatus', () => ({ @@ -41,7 +41,7 @@ jest.mock('../../lib/hooks/useFxAStatus', () => ({ default: jest.fn(), })); -jest.mock('fxa-shared/sentry/browser', () => ({ +jest.mock('@fxa/shared/sentry/browser', () => ({ __esModule: true, default: { enable: jest.fn(), diff --git a/packages/fxa-settings/src/components/App/index.tsx b/packages/fxa-settings/src/components/App/index.tsx index 28a1eabe5a3..8fb9771a1c6 100644 --- a/packages/fxa-settings/src/components/App/index.tsx +++ b/packages/fxa-settings/src/components/App/index.tsx @@ -38,7 +38,7 @@ import { } from '../../models/contexts/SettingsContext'; import { AccountStateProvider } from '../../models/contexts/AccountStateContext'; -import sentryMetrics from 'fxa-shared/sentry/browser'; +import sentryMetrics from '@fxa/shared/sentry/browser'; import { maybeRecordWebAuthnCapabilities } from '../../lib/webauthnCapabilitiesProbe'; // Components @@ -309,7 +309,8 @@ export const App = ({ recoveryKey: data.account.recoveryKey?.exists ?? false, hasSecondaryVerifiedEmail: data.account.emails.length > 1 && data.account.emails[1].verified, - totpActive: (data.account.totp?.exists && data.account.totp?.verified) ?? false, + totpActive: + (data.account.totp?.exists && data.account.totp?.verified) ?? false, }); } }, [ diff --git a/packages/fxa-settings/src/index.tsx b/packages/fxa-settings/src/index.tsx index 81423abf1d2..b009e418351 100644 --- a/packages/fxa-settings/src/index.tsx +++ b/packages/fxa-settings/src/index.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { render } from 'react-dom'; -import sentryMetrics from 'fxa-shared/sentry/browser'; +import sentryMetrics from '@fxa/shared/sentry/browser'; import { AppErrorBoundary } from './components/ErrorBoundaries'; import App from './components/App'; import { NimbusProvider } from './models/contexts/NimbusContext'; diff --git a/packages/fxa-settings/src/lib/metrics.ts b/packages/fxa-settings/src/lib/metrics.ts index 30ddec4507b..469d54fab25 100644 --- a/packages/fxa-settings/src/lib/metrics.ts +++ b/packages/fxa-settings/src/lib/metrics.ts @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import sentryMetrics from 'fxa-shared/sentry/browser'; +import sentryMetrics from '@fxa/shared/sentry/browser'; import { window } from './window'; import { v4 as uuid } from 'uuid'; import { QueryParams } from '..'; diff --git a/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/container.test.tsx b/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/container.test.tsx index a73b024f760..8fcdab00880 100644 --- a/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/container.test.tsx +++ b/packages/fxa-settings/src/pages/Signup/ConfirmSignupCode/container.test.tsx @@ -9,7 +9,7 @@ import * as HooksModule from '../../../lib/oauth/hooks'; import * as OAuthFlowRecoveryModule from '../../../lib/hooks/useOAuthFlowRecovery'; import * as CacheModule from '../../../lib/cache'; import * as ReachRouterModule from '@reach/router'; -import * as SentryModule from 'fxa-shared/sentry/browser'; +import * as SentryModule from '@fxa/shared/sentry/browser'; import * as ReactUtils from 'fxa-react/lib/utils'; import { screen, waitFor } from '@testing-library/react'; @@ -326,7 +326,9 @@ describe('confirm-signup-container', () => { .mockReturnValue({ isRecovering: false, recoveryFailed: true, - attemptOAuthFlowRecovery: jest.fn().mockResolvedValue({ success: false }), + attemptOAuthFlowRecovery: jest + .fn() + .mockResolvedValue({ success: false }), }); render(); @@ -334,7 +336,8 @@ describe('confirm-signup-container', () => { await waitFor(() => { expect(mockNavigate).toHaveBeenCalledWith('/signin', { state: { - localizedErrorMessage: 'Something went wrong. Please sign in again.', + localizedErrorMessage: + 'Something went wrong. Please sign in again.', }, }); }); diff --git a/packages/fxa-shared/package.json b/packages/fxa-shared/package.json index eb9e93c16f1..55ce92be55b 100644 --- a/packages/fxa-shared/package.json +++ b/packages/fxa-shared/package.json @@ -121,10 +121,6 @@ "import": "./dist/esm/packages/fxa-shared/log/*.js", "require": "./dist/cjs/packages/fxa-shared/log/*.js" }, - "./monitoring": { - "import": "./dist/esm/packages/fxa-shared/monitoring/index.js", - "require": "./dist/cjs/packages/fxa-shared/monitoring/index.js" - }, "./metrics/*": { "import": "./dist/esm/packages/fxa-shared/metrics/*.js", "require": "./dist/cjs/packages/fxa-shared/metrics/*.js" @@ -169,14 +165,6 @@ "import": "./dist/esm/packages/fxa-shared/scripts/*.js", "require": "./dist/cjs/packages/fxa-shared/scripts/*.js" }, - "./sentry": { - "import": "./dist/esm/packages/fxa-shared/sentry/index.js", - "require": "./dist/cjs/packages/fxa-shared/sentry/index.js" - }, - "./sentry/*": { - "import": "./dist/esm/packages/fxa-shared/sentry/*.js", - "require": "./dist/cjs/packages/fxa-shared/sentry/*.js" - }, "./speed-trap/*": { "import": "./dist/esm/packages/fxa-shared/speed-trap/*.js", "require": "./dist/cjs/packages/fxa-shared/speed-trap/*.js" diff --git a/packages/fxa-shared/sentry/browser.ts b/packages/fxa-shared/sentry/browser.ts deleted file mode 100644 index 2d23b6b1586..00000000000 --- a/packages/fxa-shared/sentry/browser.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import * as Sentry from '@sentry/browser'; -import { Integration } from '@sentry/core'; -import { SentryConfigOpts } from './models/SentryConfigOpts'; -import { ILogger } from '../log'; -import { tagFxaName } from './tag'; -import { buildSentryConfig } from './config-builder'; - -/** - * Exception fields that are imported as tags - */ -const EXCEPTION_TAGS = ['code', 'context', 'errno', 'namespace', 'status']; - -// Internal flag to keep track of whether or not sentry is initialized -let sentryEnabled = false; - -// HACK: allow tests to stub this function from Sentry -// https://stackoverflow.com/questions/35240469/how-to-mock-the-imports-of-an-es6-module -export const _Sentry = { - captureException: Sentry.captureException, - close: Sentry.close, -}; - -/** - * function that gets called before data gets sent to error metrics - * - * @param {Object} event - * Error object data - * @returns {Object} data - * Modified error object data - * @private - */ -function beforeSend( - opts: SentryConfigOpts, - event: Sentry.ErrorEvent, - hint?: Sentry.EventHint -) { - if (sentryEnabled === false) { - return null; - } - - if (event.request) { - if (event.tags) { - // if this is a known errno, then use grouping with fingerprints - // Docs: https://docs.sentry.io/hosted/learn/rollups/#fallback-grouping - if (event.tags.errno) { - event.fingerprint = ['errno' + (event.tags.errno as number)]; - // if it is a known error change the error level to info. - event.level = 'info'; - } - } - } - - event = tagFxaName(event, opts.sentry?.clientName || opts.sentry?.serverName); - return event; -} - -function captureException(err: Error) { - if (!sentryEnabled) { - return; - } - - Sentry.withScope((scope: Sentry.Scope) => { - EXCEPTION_TAGS.forEach((tagName) => { - if (tagName in err) { - scope.setTag( - tagName, - ( - err as { - [key: string]: any; - } - )[tagName] - ); - } - }); - _Sentry.captureException(err); - }); -} - -function disable() { - sentryEnabled = false; -} - -function enable() { - sentryEnabled = true; -} - -function configure(config: SentryConfigOpts, log?: ILogger) { - if (!log) { - log = console; - } - - if (!config?.sentry?.dsn) { - log.error('sentry.dsn.missing', 'No Sentry dsn provided'); - return; - } - - // We want sentry to be disabled by default... This is because we only emit data - // for users that 'have opted in'. A subsequent call to 'enable' is needed to ensure - // that sentry events only flow under the proper circumstances. - disable(); - - const opts = buildSentryConfig(config, log); - - // If tracing is configured, add the integration. - const integrations: Integration[] = []; - if (opts.tracesSampleRate || opts.tracesSampler) { - integrations.push(Sentry.browserTracingIntegration()); - } - - try { - Sentry.init({ - ...opts, - beforeSend: function (event: Sentry.ErrorEvent, hint?: Sentry.EventHint) { - return beforeSend(opts, event, hint); - }, - integrations, - }); - } catch (e) { - log.error(e); - } -} - -export default { - configure, - captureException, - disable, - enable, - __sentryEnabled: function () { - return sentryEnabled; - }, - __beforeSend: beforeSend, -}; diff --git a/packages/fxa-shared/sentry/config-builder.ts b/packages/fxa-shared/sentry/config-builder.ts deleted file mode 100644 index 055b419c7fc..00000000000 --- a/packages/fxa-shared/sentry/config-builder.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { ILogger } from '../log'; -import { SentryConfigOpts } from './models/SentryConfigOpts'; - -const sentryEnvMap: Record = { - test: 'test', - local: 'local', - dev: 'dev', - ci: 'ci', - stage: 'stage', - prod: 'prod', - production: 'prod', - development: 'dev', -}; - -function toEnv(val: any) { - if (typeof val === 'string') { - return sentryEnvMap[val] || ''; - } - return ''; -} - -export function buildSentryConfig(config: SentryConfigOpts, log: ILogger) { - if (log) { - checkSentryConfig(config, log); - } - - const opts = { - dsn: config.sentry?.dsn || '', - release: config.release || config.version, - environment: toEnv(config.sentry?.env), - sampleRate: config.sentry?.sampleRate, - clientName: config.sentry?.clientName, - serverName: config.sentry?.serverName, - fxaName: config.sentry?.clientName || config.sentry?.serverName, - tracesSampleRate: config.sentry?.tracesSampleRate, - tracesSampler: config.sentry?.tracesSampler, - }; - - return opts; -} - -function checkSentryConfig(config: SentryConfigOpts, log: ILogger) { - if (!config || !config.sentry || !config.sentry?.dsn) { - raiseError('sentry.dsn not specified. sentry disabled.'); - return; - } else { - log?.info('sentry-config-builder', { - msg: `Config setting for sentry.dsn specified, enabling sentry for env ${config.sentry.env}!`, - }); - } - - if (!config.sentry.env) { - raiseError('config missing either environment or env.'); - } else if (!toEnv(config.sentry.env)) { - raiseError( - `invalid config.env. ${config.sentry.env} options are: ${Object.keys( - sentryEnvMap - ).join(',')}` - ); - } else { - log?.info('sentry-config-builder', { - msg: 'sentry targeting: ' + sentryEnvMap[config.sentry.env], - }); - } - - if (!config.release && !config.version) { - raiseError('config missing either release or version.'); - } - - if (config.sentry?.sampleRate == null) { - raiseError('config missing sentry.sampleRate'); - } - if (!config.sentry.clientName && !config.sentry.serverName) { - raiseError('config missing either sentry.clientName or sentry.serverName'); - } - - function raiseError(msg: string) { - log?.warn('sentry-config-builder', { msg }); - if (config.sentry?.strict) { - throw new SentryConfigurationBuildError(msg); - } - } -} - -class SentryConfigurationBuildError extends Error {} diff --git a/packages/fxa-shared/sentry/index.ts b/packages/fxa-shared/sentry/index.ts deleted file mode 100644 index 48f8f605566..00000000000 --- a/packages/fxa-shared/sentry/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './tag'; -export * from './config-builder'; -export * from './models/SentryConfigOpts'; diff --git a/packages/fxa-shared/sentry/joi-message-overrides.ts b/packages/fxa-shared/sentry/joi-message-overrides.ts deleted file mode 100644 index 08cb4c44588..00000000000 --- a/packages/fxa-shared/sentry/joi-message-overrides.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import { AnySchema } from 'joi'; - -/** - * A set of default message overrides. These result in better error resolution in sentry. - */ -export const defaultMessageOverrides = { - // Override some of the message defaults. Here we remove the 'with value {:[.]}' - // portion of the message, because it causes too much fragmentation in our sentry - // errors. These should be applied to any .regex or .pattern joi validator. - // Form more context concerning overriding messages see: - // - https://joi.dev/api/?v=17.6.0#anymessagesmessages - // - https://github.com/hapijs/joi/blob/7aa36666863c1dde7e4eb02a8058e00555a99d54/lib/types/string.js#L718 - 'string.pattern.base': - '{{#label}} fails to match the required pattern: {{#regex}}', - 'string.pattern.name': '{{#label}} fails to match the {{#name}} pattern', - 'string.pattern.invert.base': - '{{#label}} matches the inverted pattern: {{#regex}}', - 'string.pattern.invert.name': - '{{#label}} matches the inverted {{#name}} pattern', -}; - -/** - * Applies a set of message overrides to the default joi message formats. - * @param data - Set of joi validators to apply message overrides to to. Note, data is mutated. - * @param overrides - Set of optional overrides, if none are provide the defaultMessageOverrides are used. - * @returns data - */ -export function overrideJoiMessages( - data: Record, - overrides?: Record -) { - Object.keys(data).forEach( - (x) => (data[x] = data[x].messages(overrides || defaultMessageOverrides)) - ); - return data; -} diff --git a/packages/fxa-shared/sentry/models/SentryConfigOpts.ts b/packages/fxa-shared/sentry/models/SentryConfigOpts.ts deleted file mode 100644 index 5534a38491e..00000000000 --- a/packages/fxa-shared/sentry/models/SentryConfigOpts.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { SamplingContext } from '@sentry/core'; - -export type SentryConfigOpts = { - /** Name of release */ - release?: string; - - /** Fall back for name of release */ - version?: string; - - /** Sentry specific settings */ - sentry?: { - /** The datasource name. This value can be obtained from the sentry portal. */ - dsn?: string; - /** The environment name. */ - env?: string; - /** The rate (as percent between 0 and 1) at which errors are sampled. Can be reduced to decrease data volume. */ - sampleRate?: number; - /** The name of the active client. */ - clientName?: string; - /** The name of the active server. */ - serverName?: string; - - /** When set to true, building a configuration will throw an error critical fields are missing. */ - strict?: boolean; - - /** The tracing sample rate. Setting this above 0 will aso result in performance metrics being captured. */ - tracesSampleRate?: number; - - /** Call back to dynamically determine sampling rate. When defined, tracesSampleRate won't be used. */ - tracesSampler?: (context: SamplingContext) => number | boolean; - }; -}; diff --git a/packages/fxa-shared/sentry/models/pii.ts b/packages/fxa-shared/sentry/models/pii.ts deleted file mode 100644 index 349cbbcc7b9..00000000000 --- a/packages/fxa-shared/sentry/models/pii.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** A general type that holds PII data. */ -export type PiiData = Record | string | undefined | null; - -/** - * Result of a filter action. - */ -export interface FilterActionResult { - /** - * The modified value - */ - val: T; - - /** - * Whether or not the pipeline can be exited. In the event the filter removes enough data, it might - * make sense to exit the pipeline of filter actions early. - */ - exitPipeline: boolean; -} - -/** A general interface for running a filter action on PII Data */ -export interface IFilterAction { - /** - * Filters a value for PII - * @param val - the value to filter - * @param depth - if filtering an object, the depth of the current traversal - * @returns the provided value with modifications, and flag if the action pipeline can be exited. - */ - execute(val: T, depth?: number): FilterActionResult; -} - -/** A general interface for top level classes that filter PII data */ -export interface IFilter { - filter(event: PiiData): PiiData; -} - -/** Things to check for when scrubbing for PII. */ -export type CheckOnly = 'keys' | 'values' | 'both'; diff --git a/packages/fxa-shared/sentry/node.ts b/packages/fxa-shared/sentry/node.ts deleted file mode 100644 index bb667634967..00000000000 --- a/packages/fxa-shared/sentry/node.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import * as Sentry from '@sentry/node'; -import { ErrorEvent } from '@sentry/core'; -import { SentryConfigOpts } from './models/SentryConfigOpts'; -import { ILogger } from '../log'; -import { tagCriticalEvent, tagFxaName } from './tag'; -import { buildSentryConfig } from './config-builder'; - -export type ExtraOpts = { - integrations?: any[]; - eventFilters?: Array<(event: ErrorEvent, hint: any) => ErrorEvent>; -}; - -export type InitSentryOpts = SentryConfigOpts & ExtraOpts; - -export function initSentry(config: InitSentryOpts, log: ILogger) { - if (!config?.sentry?.dsn) { - log.error( - 'sentry.dsn.missing', - 'No Sentry dsn provided. Cannot start sentry' - ); - return; - } - - const opts = buildSentryConfig(config, log); - const beforeSend = function (event: ErrorEvent, hint: any) { - // Default - event = tagCriticalEvent(event); - event = tagFxaName(event, config.sentry?.serverName || 'unknown'); - - // Custom filters - config.eventFilters?.forEach((filter) => { - event = filter(event, hint); - }); - return event; - }; - - const integrations = [ - // Default - Sentry.extraErrorDataIntegration({ depth: 5 }), - Sentry.requestDataIntegration(), - - // Custom Integrations - ...(config.integrations || []), - ]; - - try { - Sentry.init({ - // Defaults Options - normalizeDepth: 6, - maxValueLength: 500, - - // Custom Options - integrations, - beforeSend, - ...opts, - }); - } catch (e) { - log.debug('Issue initializing sentry!'); - log.error(e); - } -} diff --git a/packages/fxa-shared/sentry/pii-filter-actions.ts b/packages/fxa-shared/sentry/pii-filter-actions.ts deleted file mode 100644 index 5506f765bba..00000000000 --- a/packages/fxa-shared/sentry/pii-filter-actions.ts +++ /dev/null @@ -1,333 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { CheckOnly, IFilterAction, PiiData } from './models/pii'; - -/** Default replacement values */ -export const FILTERED = '[Filtered]'; -export const TRUNCATED = '[Truncated]'; - -/** - * A filter that truncates anything over maxDepth. This is a good first action. - */ -export class DepthFilter implements IFilterAction { - /** - * Crete new depth filter. - * @param maxDepth - */ - constructor(protected readonly maxDepth = 3) {} - - execute(val: T, depth: number = 1) { - let exitPipeline = false; - - if (val == null) { - exitPipeline = true; - } else if (depth >= this.maxDepth && typeof val === 'object') { - Object.keys(val)?.forEach((x) => { - val[x] = TRUNCATED; - }); - exitPipeline = true; - } - - return { val, exitPipeline }; - } -} - -/** - * A filter truncates any object or array containing too many entries. - */ -export class BreadthFilter implements IFilterAction { - /** - * Create new breadth filter - * @param maxBreadth max number of values in object / array - */ - constructor(protected readonly maxBreadth: number) {} - - execute(val: T) { - let exitPipeline = false; - - if (val == null) { - exitPipeline = true; - } else if (typeof val === 'object') { - if (val instanceof Array) { - exitPipeline = this.maxBreadth == 0 || val.length === 0; - const deleted = val.splice(this.maxBreadth); - - // Leave some indication of what was deleted - if (deleted?.length) { - val.push(`${TRUNCATED}:${deleted.length}`); - } - } else { - const keys = Object.keys(val); - let count = 0; - for (const x of keys) { - if (++count > this.maxBreadth) { - delete val[x]; - } - } - - // Leave some indication of what was deleted - if (count > this.maxBreadth) { - val[TRUNCATED] = count - this.maxBreadth; - } - - exitPipeline = keys.length === 0 || this.maxBreadth === 0; - } - } - return { val, exitPipeline }; - } -} - -/** - * A base class for other PiiFilters. Supports checking keys and values - */ -export abstract class PiiFilter implements IFilterAction { - /** Flag determining if object values should be checked. */ - protected get checkValues() { - return this.checkOnly === 'values' || this.checkOnly === 'both'; - } - - /** Flag determining if object keys should be checked. */ - protected get checkKeys() { - return this.checkOnly === 'keys' || this.checkOnly === 'both'; - } - - /** - * Creates a new regex filter action - * @param checkOnly - Optional directive indicating what to check, a value, an object key, or both. - * @param replaceWith - Optional value indicating what to replace a matched value with. - */ - constructor( - public readonly checkOnly: CheckOnly = 'values', - public readonly replaceWith = FILTERED - ) {} - - /** - * Runs the filter - * @param val - value to filter on. - * @returns a filtered value - */ - public execute(val: T) { - let exitPipeline = false; - - if (val == null) { - exitPipeline = true; - } else if (typeof val === 'string') { - val = this.replaceValues(val) as T; - exitPipeline = val === this.replaceWith; - } else if (typeof val === 'object') { - exitPipeline = true; - - // Mutate object - for (const key of Object.keys(val)) { - if (this.filterKey(key)) { - val[key] = this.replaceWith; - } else if (this.filterValue(val[key])) { - val[key] = this.replaceValues(val[key]); - } - - // Encountering a non truncated or non filtered value means the pipeline must keep running. - if (exitPipeline && val[key] !== this.replaceWith) { - exitPipeline = false; - } - } - } - - return { val, exitPipeline }; - } - - /** - * Indicates if value should be filtered - * @param val - * @returns - */ - protected filterValue(val: any) { - return this.checkValues && typeof val === 'string'; - } - - /** - * Let the sub classes determine how to replace values. - * @param val - */ - protected abstract replaceValues(val: string): string; - - /** - * Let subclasses determine when an object's key should be filtered out. - * @param key - */ - protected abstract filterKey(key: string): boolean; -} - -/** - * Uses a regular expression to scrub PII - */ -export class PiiRegexFilter extends PiiFilter implements IFilterAction { - /** - * Creates a new regex filter action - * @param regex - regular expression to use for filter - * @param checkOnly - Optional directive indicating what to check, a value, an object key, or both. - * @param replaceWith - Optional value indicating what to replace a matched value with. - */ - constructor( - public readonly regex: RegExp, - public readonly checkOnly: CheckOnly = 'values', - public readonly replaceWith = FILTERED - ) { - super(checkOnly, replaceWith); - } - - protected override replaceValues(val: string): string { - return val.replace(this.regex, this.replaceWith); - } - - protected override filterKey(key: string): boolean { - const result = this.checkKeys && this.regex.test(key); - - // Tricky edge case. The regex maybe sticky. If so, we need to reset its lastIndex so it does not - // affect a subsequent operation. - if (this.regex.sticky) { - this.regex.lastIndex = 0; - } - return result; - } -} - -/** - * Makes sure that if value is a URL it doesn't have identifying info like the username or password portion of the url. - */ -export class UrlUsernamePasswordFilter extends PiiFilter { - constructor(replaceWith = FILTERED) { - super('values', replaceWith); - } - - protected override replaceValues(val: string) { - const url = tryParseUrl(val); - if (url) { - if (url.username) { - url.username = this.replaceWith; - } - if (url.password) { - url.password = this.replaceWith; - } - val = decodeURI(url.toString()); - } - return val; - } - - protected override filterKey(): boolean { - return false; - } -} - -/** - * Strips emails from data. - */ -export class EmailFilter extends PiiRegexFilter { - private readonly encode = [`'`, `"`, `=`]; - private readonly decode = [`[[[']]]`, `[[["]]]`, `[[[=]]]`]; - - constructor(checkOnly: CheckOnly = 'values', replaceWith = FILTERED) { - super( - // RFC 5322 generalized email regex, ~ 99.99% accurate. - /(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/gim, - checkOnly, - replaceWith - ); - } - - protected override replaceValues(val: string) { - const url = tryParseUrl(val); - if (url) { - if (url.search) { - url.search = url.search.replace(this.regex, this.replaceWith); - } - if (url.pathname) { - url.pathname = url.pathname.replace(this.regex, this.replaceWith); - } - try { - val = decodeURI(url.toString()); - } catch { - // Fallback incase the replaces made the url invalid - val = url.toString(); - } - } - - // Encode/decode to work around weird cases like email='foo@bar.com' which is - // technically a valid email, but ill advised and unlikely. Even if a user had - // this odd example email, the majority of the email would stripped, for example, - // email='[Filtered]' thereby eliminating PII. - this.encode.forEach((x, i) => { - val = val.replace(x, this.decode[i]); - }); - val = val.replace(this.regex, this.replaceWith); - this.decode.forEach((x, i) => { - val = val.replace(x, this.encode[i]); - }); - return val; - } - - protected filterKey(key: string): boolean { - return false; - } -} - -/** Auxillary method for safely parsing a url. If it can't be parsed returns null. */ -function tryParseUrl(val: string) { - try { - return new URL(val); - } catch (_) { - return null; - } -} - -/** - * Some common PII scrubbing actions - */ -export const CommonPiiActions = { - /** - * Limits object/arrays no more than 50 values. - */ - breadthFilter: new BreadthFilter(50), - - /** - * Limits objects to 5 levels of depth - */ - depthFilter: new DepthFilter(6), - - /** - * Makes sure the user name / password is stripped out of the url. - */ - urlUsernamePassword: new UrlUsernamePasswordFilter(), - - /** - * Makes sure emails are stripped from data. Uses RFC 5322 generalized email regex, ~ 99.99% accurate. - */ - emailValues: new EmailFilter(), - - /** - * Matches IP V6 values - */ - ipV6Values: new PiiRegexFilter( - /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/gim - ), - - /** - * Matches IPV4 values - */ - ipV4Values: new PiiRegexFilter( - /(\b25[0-5]|\b2[0-4][0-9]|\b[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}/gim - ), - - /** - * Looks for keys that commonly contain PII - */ - piiKeys: new PiiRegexFilter( - /^oidc-.*|^remote-groups$|^uid$|^email_?|^ip_?|^user$|^user_?(id|name)$/i, - 'keys' - ), - - /** - * Matches uid, session, oauth and other common tokens which we would prefer not to include in Sentry reports. - */ - tokenValues: new PiiRegexFilter(/[a-fA-F0-9]{32,}|[a-fA-F0-9]{64,}/gim), -}; diff --git a/packages/fxa-shared/sentry/pii-filters.ts b/packages/fxa-shared/sentry/pii-filters.ts deleted file mode 100644 index 9facb930972..00000000000 --- a/packages/fxa-shared/sentry/pii-filters.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import * as Sentry from '@sentry/node'; -import { ErrorEvent } from '@sentry/core'; - -import { Message } from '@aws-sdk/client-sqs'; - -import { ILogger } from '../log'; -import { IFilterAction, PiiData } from './models/pii'; - -/** - * Base class for all filters - */ -export abstract class FilterBase { - constructor( - protected readonly actions: IFilterAction[], - protected readonly logger?: ILogger - ) {} - - /** - * Applies filters to object and drills down into object - * @param val - Value to drill into - * @param depth - the current depth in the object - * @param maxDepth - depth at which to give up - * @returns - */ - applyFilters(val: T, depth = 1, maxDepth = 10): T { - if (depth < maxDepth) { - for (const x of this.actions) { - try { - const result = x.execute(val, depth); - val = result.val; - - // Exit pipeline early if value is not longer actionable. - if (result.exitPipeline) { - break; - } - } catch (err) { - this.logger?.error('sentry.filter.error', { err }); - } - } - - if (val != null && typeof val === 'object') { - Object.values(val).forEach((x) => { - this.applyFilters(x, depth + 1, maxDepth); - }); - } - } - - return val; - } - - abstract filter(data: PiiData): PiiData; -} - -/** - * Scrubs PII from SQS Messages - */ -export class SqsMessageFilter extends FilterBase { - /** - * Create a new SqsMessageFilter - * @param actions - */ - constructor(actions: IFilterAction[]) { - super(actions); - } - - /** - * Filter Body of sqs messages - */ - public filter(event: Message) { - this.filterBody(event); - return event; - } - - protected filterBody(event: Message) { - event.Body = this.applyFilters(event.Body); - return this; - } -} diff --git a/packages/fxa-shared/sentry/report-validation-error.ts b/packages/fxa-shared/sentry/report-validation-error.ts deleted file mode 100644 index a19f3ca6741..00000000000 --- a/packages/fxa-shared/sentry/report-validation-error.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as Sentry from '@sentry/node'; -import { ValidationError } from 'joi'; - -/** - * Format a Stripe product/plan metadata validation error message for - * Sentry to include as much detail as possible about what metadata - * failed validation and in what way. - * - * @param {string} planId - * @param {string | ValidationError).ValidationError} error - */ -export function formatMetadataValidationErrorMessage( - planId: string, - error: ValidationError -) { - let msg = `${planId} metadata invalid:`; - if (typeof error === 'string') { - msg = `${msg} ${error}`; - } else { - msg = `${msg}${error.details - .map(({ message }) => ` ${message};`) - .join('')}`; - } - return msg; -} - -/** - * Report a validation error to Sentry with validation details. - * - * @param {Pick} sentry - Current sentry instance. Note, that this subtype is being - * used instead of directly accessing the sentry instance inorder to be context agnostic. - * @param {*} message - * @param {string | ValidationError} ValidationError error - */ -export function reportValidationError( - message: any, - error: ValidationError | string -) { - const details: any = {}; - if (typeof error === 'string') { - details.error = error; - } else { - for (const errorItem of error.details) { - const key = errorItem.path.join('.'); - details[key] = { - message: errorItem.message, - type: errorItem.type, - }; - } - } - - Sentry.withScope((scope) => { - scope.setContext('validationError', details); - Sentry.captureMessage(message, 'error'); - }); -} diff --git a/packages/fxa-shared/sentry/tag.ts b/packages/fxa-shared/sentry/tag.ts deleted file mode 100644 index 2ce663cb5bd..00000000000 --- a/packages/fxa-shared/sentry/tag.ts +++ /dev/null @@ -1,107 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const CRITICAL_ENDPOINTS = [ - // PORTS: 1111, 1112, 1113 - '/v1/avatar', - '/v1/profile', - '/v1/avatar/upload', - '/v1/display_name', - '/a/', - // PORTS: 3000, 3030 - '/', - '/settings', - '/settings/two_step_authentication/replace_codes', - '/sockjs-node', - '/authorization', - '/complete_reset_password', - '/metrics-flow', - '/settings', - '/settings/two_step_authentication/replace_codes', - '/signin', - '/metrics', - // PORTS: 4100 - '/', - // PORTS: 8080 - '/', - '/api/auth_status', - '/api/email_first', - '/api/oauth', - '/api/todos/get', - '/api/logout', - // PORTS: 9000, 9001 - '/v1/recoveryKey', - '/v1/account', - '/v1/account/attached_clients', - '/v1/account/keys', - '/v1/account/profile', - '/v1/client/', - '/v1/jwks', - '/v1/oauth/subscriptions/clients', - '/v1/password/forgot/status', - '/v1/recovery_email/status', - '/v1/recoveryKey/', - '/v1/totp/exists', - '/v1/account/attached_client/destroy', - '/v1/account/create', - '/v1/account/destroy', - '/v1/account/device', - '/v1/account/login', - '/v1/account/login/send_unblock_code', - '/v1/account/reset', - '/v1/account/status', - '/v1/oauth/authorization', - '/v1/oauth/token', - '/v1/password/change/finish', - '/v1/password/change/start', - '/v1/password/forgot/send_code', - '/v1/password/forgot/verify_code', - '/v1/recovery_email', - '/v1/recovery_email/destroy', - '/v1/recovery_email/secondary/verify_code', - '/v1/recovery_email/set_primary', - '/v1/recoveryKey', - '/v1/recoveryKey/exists', - '/v1/session/destroy', - '/v1/session/reauth', - '/v1/session/verify/recoveryCode', - '/v1/session/verify/totp', - '/v1/token', - '/v1/totp/create', - '/v1/totp/destroy', - '/v1/verify', - '/v1/recoveryCodes', - '/mail/', -]; - -/** - * Checks to see if the event is for a known critical endpoint. If so, - * tries to add a critical. - * @param data - */ -export function tagCriticalEvent(data: any) { - if (data && data.request && data.request.url) { - // It's possible value cannot be parsed. - let url: URL | undefined; - try { - url = new URL(data.request.url); - } catch (_err) {} - - if ( - url && - url.pathname && - CRITICAL_ENDPOINTS.some((x) => url?.pathname.startsWith(x)) - ) { - data.tags = data.tags || {}; - data.tags['fxa.endpoint.type'] = 'critical'; - } - } - - return data; -} - -export function tagFxaName(data: any, name?: string) { - data.tags = data.tags || {}; - data.tags['fxa.name'] = name || 'unknown'; - return data; -} diff --git a/packages/fxa-shared/test/sentry/browser.ts b/packages/fxa-shared/test/sentry/browser.ts deleted file mode 100644 index 1438ff3af89..00000000000 --- a/packages/fxa-shared/test/sentry/browser.ts +++ /dev/null @@ -1,112 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import 'jsdom-global/register'; -import { assert } from 'chai'; -import * as Sentry from '@sentry/browser'; -import sentryMetrics, { _Sentry } from '../../sentry/browser'; -import { SentryConfigOpts } from '../../sentry'; -import { ILogger } from '../../log'; -const sinon = require('sinon'); -const sandbox = sinon.createSandbox(); - -const config: SentryConfigOpts = { - release: 'v0.0.0', - sentry: { - dsn: 'https://public:private@host:8080/1', - env: 'test', - clientName: 'fxa-shared-testing', - sampleRate: 0, - }, -}; -const logger: ILogger = { - info(...args: any) {}, - trace(...args: any) {}, - warn(...args: any) {}, - error(...args: any) {}, - debug(...args: any) {}, -}; - -describe('sentry/browser', () => { - before(() => { - // Reduce console log noise in test output - sandbox.spy(console, 'error'); - }); - - beforeEach(() => { - // Make sure it's enabled by default - sentryMetrics.enable(); - }); - - after(() => { - sandbox.restore(); - }); - - describe('init', () => { - it('properly configures with dsn', () => { - sentryMetrics.configure(config, logger); - }); - }); - - describe('beforeSend', () => { - before(() => { - sentryMetrics.configure(config, logger); - }); - - it('works without request url', () => { - const data = { - key: 'value', - } as unknown as Sentry.ErrorEvent; - - const resultData = sentryMetrics.__beforeSend(config, data, {}); - - assert.equal(data, resultData); - }); - - it('fingerprints errno', () => { - const data = { - request: { - url: 'https://example.com', - }, - tags: { - errno: '100', - }, - } as unknown as Sentry.ErrorEvent; - - const resultData = sentryMetrics.__beforeSend(config, data, {}); - - assert.equal( - resultData!.fingerprint![0], - 'errno100', - 'correct fingerprint' - ); - assert.equal(resultData!.level, 'info', 'correct known error level'); - }); - }); - - describe('captureException', () => { - it('calls Sentry.captureException', () => { - const sentryCaptureException = sinon.stub(_Sentry, 'captureException'); - sentryMetrics.captureException(new Error('testo')); - sinon.assert.calledOnce(sentryCaptureException); - sentryCaptureException.restore(); - }); - }); - - describe('disable / enables', () => { - it('enables', () => { - sentryMetrics.enable(); - assert.isTrue(sentryMetrics.__sentryEnabled()); - }); - - it('disables', () => { - sentryMetrics.disable(); - assert.isFalse(sentryMetrics.__sentryEnabled()); - }); - - it('will return null from before send when disabled', () => { - sentryMetrics.disable(); - assert.isNull(sentryMetrics.__beforeSend({}, {} as any, {})); - }); - }); -}); diff --git a/packages/fxa-shared/test/sentry/config-builder.ts b/packages/fxa-shared/test/sentry/config-builder.ts deleted file mode 100644 index 1b9859a7a4c..00000000000 --- a/packages/fxa-shared/test/sentry/config-builder.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { assert } from 'chai'; -import { buildSentryConfig, SentryConfigOpts } from '../../sentry'; -import Sinon, { SinonSpiedInstance } from 'sinon'; -import { ILogger } from '../../log'; - -describe('config-builder', () => { - function cloneConfig(val: any) { - return JSON.parse(JSON.stringify(val)); - } - - const emptyLogger: ILogger = { - info: function (...args: any): void {}, - trace: function (...args: any): void {}, - warn: function (...args: any): void { - console.log('WARNING', ...args); - }, - error: function (...args: any): void {}, - debug: function (...args: any): void {}, - }; - - const sandbox = Sinon.createSandbox(); - const loggerSpy: SinonSpiedInstance = sandbox.spy(emptyLogger); - - afterEach(() => { - sandbox.reset(); - }); - - const testConfig: SentryConfigOpts = { - release: '1.0.1', - version: '1.0.2', - sentry: { - dsn: 'https://foo.sentry.io', - env: 'test', - sampleRate: 1, - serverName: 'fxa-shared-test', - clientName: 'fxa-shared-client-test', - }, - }; - - it('builds', () => { - const config = buildSentryConfig(testConfig, loggerSpy); - assert.exists(config); - assert.isTrue(loggerSpy.info.called); - }); - - it('picks correct defaults', () => { - const config = buildSentryConfig(testConfig, loggerSpy); - assert.equal(config.environment, testConfig.sentry?.env); - assert.equal(config.release, testConfig.release); - assert.equal(config.fxaName, testConfig.sentry?.clientName); - }); - - it('falls back', () => { - const clone = cloneConfig(testConfig); - delete clone.sentry.clientName; - delete clone.release; - - const config = buildSentryConfig(clone, loggerSpy); - - assert.equal(config.release, testConfig.version); - assert.equal(config.fxaName, testConfig.sentry?.serverName); - }); - - it('warns about missing config', () => { - const clone = cloneConfig(testConfig); - clone.sentry.dsn = ''; - - buildSentryConfig(clone, loggerSpy); - - assert.isTrue(loggerSpy.warn.called); - }); - - it('errors on missing dsn', () => { - const clone = JSON.parse(JSON.stringify(testConfig)); - clone.sentry.strict = true; - clone.sentry.dsn = ''; - - assert.throws(() => { - buildSentryConfig(clone, loggerSpy); - }, 'sentry.dsn not specified. sentry disabled.'); - assert.isTrue(loggerSpy.warn.called); - }); - - it('errors on unknown environment', () => { - const clone = cloneConfig(testConfig); - clone.sentry.strict = true; - clone.sentry.env = 'xyz'; - - assert.throws(() => { - buildSentryConfig(clone, loggerSpy); - }, 'invalid config.env. xyz options are: test,local,dev,ci,stage,prod,production,development'); - assert.isTrue(loggerSpy.warn.called); - }); - - it('errors on missing release', () => { - const clone = cloneConfig(testConfig); - clone.sentry.strict = true; - delete clone.release; - delete clone.version; - - assert.throws(() => { - buildSentryConfig(clone, loggerSpy); - }, 'config missing either release or version.'); - assert.isTrue(loggerSpy.warn.called); - }); - - it('errors on missing sampleRate', () => { - const clone = cloneConfig(testConfig); - clone.sentry.strict = true; - delete clone.sentry.sampleRate; - - assert.throws(() => { - buildSentryConfig(clone, loggerSpy); - }, 'sentry.sampleRate'); - assert.isTrue(loggerSpy.warn.called); - }); - - it('can use mozlogger', () => { - const mozlog = require('mozlog')({ - app: 'fxa-shared-test', - level: 'trace', - }); - const logger = mozlog('fxa-shared-testing'); - const config = buildSentryConfig(testConfig, logger); - - assert.exists(config); - }); - - it('can use console logger', () => { - const config = buildSentryConfig(testConfig, console); - assert.exists(config); - }); -}); diff --git a/packages/fxa-shared/test/sentry/event-tagging.ts b/packages/fxa-shared/test/sentry/event-tagging.ts deleted file mode 100644 index fc0f6d45e3b..00000000000 --- a/packages/fxa-shared/test/sentry/event-tagging.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { assert } from 'chai'; -import { Event } from '@sentry/core'; -import { tagCriticalEvent } from '../../sentry'; - -describe('critical-endpoints', () => { - it('adds critical tag to applicable event', () => { - let data: any = { - request: { - url: 'https://example.com/a/123', - }, - }; - - data = tagCriticalEvent(data); - - assert.exists(data.tags?.['fxa.endpoint.type']); - assert.equal(data.tags?.['fxa.endpoint.type'], 'critical'); - }); - - it('does not add critical tag to no applicable event', () => { - const data: Event = { - request: { - url: 'https://example.com/a-non-critical-endpoint', - }, - }; - - tagCriticalEvent(data); - - assert.notExists(data.tags?.['fxa.endpoint.importance']); - }); - - it('handles empty event', () => { - const data: Event = {}; - - tagCriticalEvent(data); - - assert.notExists(data.tags?.['fxa.endpoint.importance']); - }); - - it('handles empty url', () => { - const data: Event = { - request: { - url: undefined, - }, - }; - - tagCriticalEvent(data); - - assert.notExists(data.tags?.['fxa.endpoint.importance']); - }); -}); diff --git a/packages/fxa-shared/test/sentry/joi-message-overrides.ts b/packages/fxa-shared/test/sentry/joi-message-overrides.ts deleted file mode 100644 index 5f27a7e00fc..00000000000 --- a/packages/fxa-shared/test/sentry/joi-message-overrides.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import { assert } from 'chai'; -import joi, { valid } from 'joi'; -import { overrideJoiMessages } from '../../sentry/joi-message-overrides'; - -describe('joi-message-overrides', () => { - it('overrides default message for regex', () => { - const validators = { - test: joi.string().regex(/test/), - }; - const result1 = validators.test.validate('foobar').error?.message; - - const validators2 = overrideJoiMessages(validators); - const result2 = validators2.test.validate('foobar').error?.message; - - assert.exists(validators2); - assert.exists(result1); - assert.exists(result2); - assert.notEqual(result1, result2); - assert.include(result1, 'with value'); - assert.notInclude(result2, 'with value'); - }); -}); diff --git a/packages/fxa-shared/test/sentry/pii-filter-actions.ts b/packages/fxa-shared/test/sentry/pii-filter-actions.ts deleted file mode 100644 index d036ecc5df2..00000000000 --- a/packages/fxa-shared/test/sentry/pii-filter-actions.ts +++ /dev/null @@ -1,381 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { expect } from 'chai'; -import * as uuid from 'uuid'; - -import { - CommonPiiActions, - DepthFilter, - TRUNCATED, - FILTERED, - PiiRegexFilter, - BreadthFilter, -} from '../../sentry/pii-filter-actions'; - -describe('pii-filter-actions', () => { - describe('DepthFilter', () => { - it('truncates objects', () => { - const filter = new DepthFilter(1); - - expect(filter.execute('foo', 1)).to.deep.equal({ - val: 'foo', - exitPipeline: false, - }); - expect(filter.execute(null, 1)).to.deep.equal({ - val: null, - exitPipeline: true, - }); - }); - - it('truncates objects when depth is greater than or equal to max depth', () => { - const filter = new DepthFilter(1); - expect(filter.execute({ foo: 'bar' }, 1)).to.deep.equal({ - val: { - foo: TRUNCATED, - }, - exitPipeline: true, - }); - }); - - it('does not truncate if depth is less than max depth ', () => { - const filter = new DepthFilter(1); - expect(filter.execute({ foo: 'bar' }, 0)).to.deep.equal({ - val: { foo: 'bar' }, - exitPipeline: false, - }); - }); - - it('handles null', () => { - const filter = new DepthFilter(1); - expect(filter.execute(null, 1)).to.deep.equal({ - val: null, - exitPipeline: true, - }); - }); - }); - - describe('BreadthFilter', () => { - it('truncates objects', () => { - const filter = new BreadthFilter(1); - - expect(filter.execute('foo')).to.deep.equal({ - val: 'foo', - exitPipeline: false, - }); - expect(filter.execute(null)).to.deep.equal({ - val: null, - exitPipeline: true, - }); - }); - - it('truncates object of size greater than max breadth', () => { - const filter = new BreadthFilter(1); - expect(filter.execute({ foo: '1', bar: '2', baz: '3' })).to.deep.equal({ - val: { - foo: '1', - [TRUNCATED]: 2, - }, - exitPipeline: false, - }); - }); - - it('does not truncate object of size equal to max breadth', () => { - const filter = new BreadthFilter(3); - expect(filter.execute(['foo', 'bar', 'baz'])).to.deep.equal({ - val: ['foo', 'bar', 'baz'], - exitPipeline: false, - }); - }); - - it('does not truncate object of size less than max breadth', () => { - const filter = new BreadthFilter(5); - expect(filter.execute(['foo', 'bar', 'baz'])).to.deep.equal({ - val: ['foo', 'bar', 'baz'], - exitPipeline: false, - }); - }); - - it('truncates array of size greater than max breadth', () => { - const filter = new BreadthFilter(1); - expect(filter.execute(['foo', 'bar', 'baz'])).to.deep.equal({ - val: ['foo', `${TRUNCATED}:2`], - exitPipeline: false, - }); - }); - - it('does not truncate array of size less than max breadth', () => { - const filter = new BreadthFilter(5); - expect(filter.execute(['foo', 'bar', 'baz'])).to.deep.equal({ - val: ['foo', 'bar', 'baz'], - exitPipeline: false, - }); - }); - - it('does not truncate array of size equal to max breadth', () => { - const filter = new BreadthFilter(3); - expect(filter.execute(['foo', 'bar', 'baz'])).to.deep.equal({ - val: ['foo', 'bar', 'baz'], - exitPipeline: false, - }); - }); - - it('handles empty array', () => { - const filter = new BreadthFilter(1); - expect(filter.execute([])).to.deep.equal({ - val: [], - exitPipeline: true, - }); - }); - - it('handles empty object', () => { - const filter = new BreadthFilter(1); - expect(filter.execute({})).to.deep.equal({ - val: {}, - exitPipeline: true, - }); - }); - }); - - describe('PiiRegexFilter', () => { - it('filters string', () => { - const filter = new PiiRegexFilter(/foo/gi, 'values', '[BAR]'); - const value = filter.execute('test foo regex filter'); - expect(value).to.deep.equal({ - val: 'test [BAR] regex filter', - exitPipeline: false, - }); - }); - - it('filters string and determines exitPipeline', () => { - const filter = new PiiRegexFilter(/foo/gi, 'values', '[BAR]'); - const value = filter.execute('foo'); - expect(value).to.deep.equal({ val: '[BAR]', exitPipeline: true }); - }); - - it('filters object value', () => { - const filter1 = new PiiRegexFilter(/foo/gi, 'both', '[BAR]'); - const filter2 = new PiiRegexFilter(/foo/gi, 'values', '[BAR]'); - - const { val: value1 } = filter1.execute({ - item: 'test foo regex filter', - }); - const { val: value2 } = filter2.execute({ - item: 'test foo regex filter', - }); - - expect(value1.item).to.equal('test [BAR] regex filter'); - expect(value2.item).to.equal('test [BAR] regex filter'); - }); - - it('filters object key', () => { - const filter = new PiiRegexFilter(/foo/gi, 'keys', '[BAR]'); - - const { val: value } = filter.execute({ - foo: 'test foo regex filter', - }); - - expect(value.foo).to.equal('[BAR]'); - }); - - describe('checksOn', () => { - it('checks on values', () => { - const filter = new PiiRegexFilter(/foo/gi, 'values', '[BAR]'); - const { val: value } = filter.execute({ - foo: 'test foo regex filter', - bar: 'test foo regex filter', - }); - expect(value.foo).to.equal('test [BAR] regex filter'); - expect(value.bar).to.equal('test [BAR] regex filter'); - }); - - it('checks on keys', () => { - const filter = new PiiRegexFilter(/foo/gi, 'keys', '[BAR]'); - const { val: value } = filter.execute({ - foo: 'test foo regex filter', - bar: 'test foo regex filter', - }); - expect(value.foo).to.equal('[BAR]'); - expect(value.bar).to.equal('test foo regex filter'); - }); - - it('checks on keys and values', () => { - const filter = new PiiRegexFilter(/foo/gi, 'both', '[BAR]'); - const { val: value } = filter.execute({ - foo: 'test foo regex filter', - bar: 'test foo regex filter', - }); - expect(value.foo).to.equal('[BAR]'); - expect(value.bar).to.equal('test [BAR] regex filter'); - }); - }); - }); - - describe('CommonPiiActions', () => { - it('filters emails', () => { - const { val: result } = CommonPiiActions.emailValues.execute({ - foo: 'email: test@123.com -- 123@test.com --', - bar: '123', - }); - - expect(result).to.deep.equal({ - foo: `email: ${FILTERED} -- ${FILTERED} --`, - bar: '123', - }); - }); - - it('filters email in url', () => { - const { val: result } = CommonPiiActions.emailValues.execute( - 'http://foo.bar/?email=foxkey@mozilla.com&key=1' - ); - expect(result).to.equal(`http://foo.bar/?${FILTERED}&key=1`); - }); - - it('filters email in route', () => { - const { val: result } = CommonPiiActions.emailValues.execute( - '/account?email=foxkey@mozilla.com&key=1' - ); - expect(result).to.equal(`/account?email=${FILTERED}&key=1`); - }); - - it('filters email in query', () => { - const { val: result } = CommonPiiActions.emailValues.execute( - `where email='test@mozilla.com'` - ); - - expect(result).to.equal(`where email='${FILTERED}'`); - }); - - it('filters username / password from url', () => { - const { val: result } = CommonPiiActions.urlUsernamePassword.execute( - 'http://me:wut@foo.bar/' - ); - expect(result).to.equal(`http://${FILTERED}:${FILTERED}@foo.bar/`); - }); - - it('ipv6 values', () => { - const { val: result } = CommonPiiActions.ipV6Values.execute({ - foo: 'ipv6: 2001:0db8:85a3:0000:0000:8a2e:0370:7334 -- FE80:0000:0000:0000:0202:B3FF:FE1E:8329 --', - bar: '123', - }); - expect(result).to.deep.equal({ - foo: `ipv6: ${FILTERED} -- ${FILTERED} --`, - bar: '123', - }); - }); - - it('ipv4 values', () => { - const { val: result } = CommonPiiActions.ipV4Values.execute({ - foo: '-- 127.0.0.1 -- 10.0.0.1 -- ', - bar: '1.2.3', - }); - expect(result).to.deep.equal({ - foo: `-- ${FILTERED} -- ${FILTERED} -- `, - bar: '1.2.3', - }); - }); - - it('filters pii keys', () => { - const { val: result } = CommonPiiActions.piiKeys.execute({ - 'oidc-test': 'foo', - 'OIDC-TEST': 'foo', - 'remote-groups': 'foo', - 'REMOTE-GROUPS': 'foo', - email_address: 'foo', - email: 'foo', - EmailAddress: 'foo', - ip: 'foo', - ip_addr: 'foo', - ip_address: 'foo', - IpAddress: 'foo', - uid: 'foo', - user: 'foo', - username: 'foo', - user_name: 'foo', - UserName: 'foo', - userid: 'foo', - UserId: 'foo', - user_id: 'foo', - bar: '123', - }); - - expect(result).to.deep.equal({ - 'oidc-test': FILTERED, - 'OIDC-TEST': FILTERED, - 'remote-groups': FILTERED, - 'REMOTE-GROUPS': FILTERED, - email: FILTERED, - email_address: FILTERED, - EmailAddress: FILTERED, - ip: FILTERED, - ip_addr: FILTERED, - ip_address: FILTERED, - IpAddress: FILTERED, - uid: FILTERED, - user: FILTERED, - username: FILTERED, - user_name: FILTERED, - UserName: FILTERED, - userid: FILTERED, - user_id: FILTERED, - UserId: FILTERED, - bar: '123', - }); - }); - - it('filters token values', () => { - const token1 = uuid.v4().replace(/-/g, ''); - const token2 = uuid.v4().replace(/-/g, ''); - const token3 = uuid.v4().toString(); - const { val: result } = CommonPiiActions.tokenValues.execute({ - foo: `-- ${token1}\n${token2}--`, - bar: token3, - }); - - expect(result).to.deep.equal({ - foo: `-- ${FILTERED}\n${FILTERED}--`, - bar: token3, - }); - }); - - it('filters 64 byte token values', () => { - const token1 = uuid.v4().replace(/-/g, ''); - const { val: result } = CommonPiiActions.tokenValues.execute({ - foo: `X'${token1}${token1}'`, - }); - - expect(result).to.deep.equal({ - foo: `X'${FILTERED}'`, - }); - }); - - it('filters token value in url', () => { - const result = CommonPiiActions.tokenValues.execute( - 'https://foo.bar/?uid=12345678123456781234567812345678' - ); - expect(result.val).to.equal(`https://foo.bar/?uid=${FILTERED}`); - }); - - it('filters token value in db statement', () => { - const result = CommonPiiActions.tokenValues.execute( - `Call accountDevices_17(X'cce22e4006d243c895c7596e2cad53d8',500)` - ); - expect(result.val).to.equal(`Call accountDevices_17(X'${FILTERED}',500)`); - }); - - it('filters token value in db query', () => { - const result = CommonPiiActions.tokenValues.execute( - ` where uid = X'cce22e4006d243c895c7596e2cad53d8' ` - ); - expect(result.val).to.equal(` where uid = X'${FILTERED}' `); - }); - - it('filters multiple multiline token values', () => { - const token = '12345678123456781234567812345678'; - const { val: result } = CommonPiiActions.tokenValues.execute( - `${token}--${token}\n${token}` - ); - expect(result).to.equal(`${FILTERED}--${FILTERED}\n${FILTERED}`); - }); - }); -}); diff --git a/packages/fxa-shared/test/sentry/pii-filters.ts b/packages/fxa-shared/test/sentry/pii-filters.ts deleted file mode 100644 index f293bc62a64..00000000000 --- a/packages/fxa-shared/test/sentry/pii-filters.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { ErrorEvent } from '@sentry/core'; -import { Message } from '@aws-sdk/client-sqs'; -import { expect } from 'chai'; -import sinon from 'sinon'; - -import { ILogger } from '../../log'; -import { IFilterAction, PiiData } from '../../sentry/models/pii'; -import { - CommonPiiActions, - FILTERED, - PiiRegexFilter, - TRUNCATED, -} from '../../sentry/pii-filter-actions'; -import { FilterBase, SqsMessageFilter } from '../../sentry/pii-filters'; - -describe('pii-filters', () => { - describe('SqsMessageFilter', () => { - const sqsFilter = new SqsMessageFilter([new PiiRegexFilter(/foo/gi)]); - - it('filters body', () => { - let msg = { Body: 'A message with foo in it.' } as Message; - msg = sqsFilter.filter(msg); - - expect(msg).to.deep.equal({ - Body: `A message with ${FILTERED} in it.`, - }); - }); - }); - - describe('Deals with Bad Filter', () => { - let mockLogger = { - error: () => {}, - }; - let sandbox = sinon.createSandbox(); - - afterEach(() => { - sandbox.restore(); - }); - - class BadAction implements IFilterAction { - execute( - val: T, - depth?: number - ): { val: T; exitPipeline: boolean } { - throw new Error('Boom'); - } - } - - class BadFilter extends FilterBase { - constructor(logger: ILogger) { - super([new BadAction()], logger); - } - - filter(data: any): any { - return this.applyFilters(data); - } - } - - it('handles errors and logs them', () => { - const errorStub = sandbox.stub(mockLogger, 'error'); - const badFilter = new BadFilter(mockLogger); - badFilter.filter({ foo: 'bar' }); - expect(errorStub.called).to.be.true; - }); - }); -}); diff --git a/packages/fxa-shared/tracing/pii-filters.ts b/packages/fxa-shared/tracing/pii-filters.ts index 73752451c3c..f23257a7dc8 100644 --- a/packages/fxa-shared/tracing/pii-filters.ts +++ b/packages/fxa-shared/tracing/pii-filters.ts @@ -3,9 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { ILogger } from '../log'; -import { PiiData } from '../sentry/models/pii'; -import { CommonPiiActions } from '../sentry/pii-filter-actions'; -import { FilterBase } from '../sentry/pii-filters'; +import { PiiData } from '@fxa/shared/sentry-utils'; +import { CommonPiiActions } from '@fxa/shared/sentry-utils'; +import { FilterBase } from '@fxa/shared/sentry-utils'; /** Matches attribute names that need to be filtered. */ const reTargetPiiAttributes = /^(db|http)\./; diff --git a/tsconfig.base.json b/tsconfig.base.json index d56fc4fe526..32ffdf19015 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -90,6 +90,7 @@ "@fxa/shared/log": ["libs/shared/log/src/index.ts"], "@fxa/shared/metrics/glean": ["libs/shared/metrics/glean/src/index.ts"], "@fxa/shared/metrics/statsd": ["libs/shared/metrics/statsd/src/index.ts"], + "@fxa/shared/monitoring": ["libs/shared/monitoring/src/index.ts"], "@fxa/shared/mozlog": ["libs/shared/mozlog/src/index.ts"], "@fxa/shared/nestjs/customs": ["libs/shared/nestjs/customs/src/index.ts"], "@fxa/shared/notifier": ["libs/shared/notifier/src/index.ts"], @@ -103,7 +104,12 @@ "@fxa/shared/sentry-next": ["libs/shared/sentry-next/src/index.ts"], "@fxa/shared/sentry-node": ["libs/shared/sentry-node/src/index.ts"], "@fxa/shared/sentry-utils": ["libs/shared/sentry-utils/src/index.ts"], + "@fxa/shared/sentry/browser": ["libs/shared/sentry/src/lib/browser.ts"], "@fxa/shared/sentry/client": ["libs/shared/sentry/src/client.ts"], + "@fxa/sentry-browser": ["libs/shared/sentry-browser/src/index.ts"], + "@fxa/sentry-nest": ["libs/shared/sentry-nest/src/index.ts"], + "@fxa/sentry-node": ["libs/shared/sentry-node/src/index.ts"], + "@fxa/sentry-utils": ["libs/shared/sentry-utils/src/index.ts"], "@fxa/vendored/common-password-list": [ "libs/vendored/common-password-list/src/index.ts" ],