From d0ca42db9207763d5858130e282a48e61fb3b3f0 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 12 Mar 2024 10:15:59 -0400 Subject: [PATCH 1/7] feat(v8): Add @sentry/aws-serverless package --- .craft.yml | 4 +- .github/ISSUE_TEMPLATE/bug.yml | 2 +- README.md | 6 +- .../node-exports-test-app/package.json | 2 +- .../scripts/consistentExports.ts | 8 +- .../e2e-tests/verdaccio-config/config.yaml | 2 +- dev-packages/rollup-utils/bundleHelpers.mjs | 2 +- .../.eslintrc.js | 0 .../{serverless => aws-serverless}/LICENSE | 0 .../{serverless => aws-serverless}/README.md | 48 +- .../jest.config.js | 0 .../package.json | 11 +- .../rollup.aws.config.mjs | 8 +- .../rollup.npm.config.mjs | 0 .../scripts/buildLambdaLayer.ts | 12 +- .../src/awslambda-auto.ts | 8 +- .../src/awslambda.ts | 22 +- .../src/awsservices.ts | 17 +- .../src/debug-build.ts | 0 .../src/index.awslambda.ts | 0 .../src/index.ts | 110 ++-- packages/aws-serverless/src/utils.ts | 14 + .../test/__mocks__/dns.ts | 0 .../test/awslambda.test.ts | 8 +- .../test/awsservices.test.ts | 6 +- .../tsconfig.json | 0 .../tsconfig.test.json | 0 .../tsconfig.types.json | 0 packages/node/test/index.test.ts | 10 +- .../src/gcpfunction/cloud_events.ts | 81 --- packages/serverless/src/gcpfunction/events.ts | 86 --- .../serverless/src/gcpfunction/general.ts | 47 -- packages/serverless/src/gcpfunction/http.ts | 99 --- packages/serverless/src/gcpfunction/index.ts | 48 -- packages/serverless/src/google-cloud-grpc.ts | 145 ----- packages/serverless/src/google-cloud-http.ts | 86 --- packages/serverless/src/utils.ts | 54 -- packages/serverless/test/gcpfunction.test.ts | 595 ------------------ .../serverless/test/google-cloud-grpc.test.ts | 156 ----- .../serverless/test/google-cloud-http.test.ts | 93 --- packages/serverless/test/private.pem | 15 - 41 files changed, 126 insertions(+), 1679 deletions(-) rename packages/{serverless => aws-serverless}/.eslintrc.js (100%) rename packages/{serverless => aws-serverless}/LICENSE (100%) rename packages/{serverless => aws-serverless}/README.md (57%) rename packages/{serverless => aws-serverless}/jest.config.js (100%) rename packages/{serverless => aws-serverless}/package.json (89%) rename packages/{serverless => aws-serverless}/rollup.aws.config.mjs (88%) rename packages/{serverless => aws-serverless}/rollup.npm.config.mjs (100%) rename packages/{serverless => aws-serverless}/scripts/buildLambdaLayer.ts (84%) rename packages/{serverless => aws-serverless}/src/awslambda-auto.ts (65%) rename packages/{serverless => aws-serverless}/src/awslambda.ts (95%) rename packages/{serverless => aws-serverless}/src/awsservices.ts (86%) rename packages/{serverless => aws-serverless}/src/debug-build.ts (100%) rename packages/{serverless => aws-serverless}/src/index.awslambda.ts (100%) rename packages/{serverless => aws-serverless}/src/index.ts (60%) create mode 100644 packages/aws-serverless/src/utils.ts rename packages/{serverless => aws-serverless}/test/__mocks__/dns.ts (100%) rename packages/{serverless => aws-serverless}/test/awslambda.test.ts (98%) rename packages/{serverless => aws-serverless}/test/awsservices.test.ts (97%) rename packages/{serverless => aws-serverless}/tsconfig.json (100%) rename packages/{serverless => aws-serverless}/tsconfig.test.json (100%) rename packages/{serverless => aws-serverless}/tsconfig.types.json (100%) delete mode 100644 packages/serverless/src/gcpfunction/cloud_events.ts delete mode 100644 packages/serverless/src/gcpfunction/events.ts delete mode 100644 packages/serverless/src/gcpfunction/general.ts delete mode 100644 packages/serverless/src/gcpfunction/http.ts delete mode 100644 packages/serverless/src/gcpfunction/index.ts delete mode 100644 packages/serverless/src/google-cloud-grpc.ts delete mode 100644 packages/serverless/src/google-cloud-http.ts delete mode 100644 packages/serverless/src/utils.ts delete mode 100644 packages/serverless/test/gcpfunction.test.ts delete mode 100644 packages/serverless/test/google-cloud-grpc.test.ts delete mode 100644 packages/serverless/test/google-cloud-http.test.ts delete mode 100644 packages/serverless/test/private.pem diff --git a/.craft.yml b/.craft.yml index b7a39df9db62..3da1906f3bad 100644 --- a/.craft.yml +++ b/.craft.yml @@ -89,8 +89,8 @@ targets: ## 5. Node-based Packages - name: npm - id: '@sentry/serverless' - includeNames: /^sentry-serverless-\d.*\.tgz$/ + id: '@sentry/aws-serverless' + includeNames: /^sentry-aws-serverless-\d.*\.tgz$/ - name: npm id: '@sentry/google-cloud' includeNames: /^sentry-google-cloud-\d.*\.tgz$/ diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 033786bad1ec..fa93f1fdb5db 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -34,6 +34,7 @@ body: - '@sentry/astro' - '@sentry/angular' - '@sentry/angular-ivy' + - '@sentry/aws-serverless' - '@sentry/bun' - '@sentry/deno' - '@sentry/ember' @@ -42,7 +43,6 @@ body: - '@sentry/node' - '@sentry/react' - '@sentry/remix' - - '@sentry/serverless' - '@sentry/google-cloud' - '@sentry/svelte' - '@sentry/sveltekit' diff --git a/README.md b/README.md index 807c4b6988e2..eccf607a4b8e 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,10 @@ package. Please refer to the README and instructions of those SDKs for more deta - [`@sentry/gatsby`](https://github.com/getsentry/sentry-javascript/tree/master/packages/gatsby): SDK for Gatsby - [`@sentry/nextjs`](https://github.com/getsentry/sentry-javascript/tree/master/packages/nextjs): SDK for Next.js - [`@sentry/remix`](https://github.com/getsentry/sentry-javascript/tree/master/packages/remix): SDK for Remix -- [`@sentry/serverless`](https://github.com/getsentry/sentry-javascript/tree/master/packages/serverless): SDK for - Serverless Platforms (AWS, GCP) +- [`@sentry/aws-serverless`](https://github.com/getsentry/sentry-javascript/tree/master/packages/aws-serverless): SDK + for AWS Lambda Functions +- [`@sentry/google-cloud`](https://github.com/getsentry/sentry-javascript/tree/master/packages/google-cloud): SDK for + Google Cloud Functions - [`@sentry/electron`](https://github.com/getsentry/sentry-electron): SDK for Electron with support for native crashes - [`@sentry/react-native`](https://github.com/getsentry/sentry-react-native): SDK for React Native with support for native crashes diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/package.json b/dev-packages/e2e-tests/test-applications/node-exports-test-app/package.json index f9bef186e506..d3279d0d9a4a 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/package.json +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/package.json @@ -18,7 +18,7 @@ "@sentry/remix": "latest || *", "@sentry/astro": "latest || *", "@sentry/nextjs": "latest || *", - "@sentry/serverless": "latest || *", + "@sentry/aws-serverless": "latest || *", "@sentry/google-cloud": "latest || *", "@sentry/bun": "latest || *", "@sentry/types": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts index 6d782a7ca0ad..5fb4adb20384 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts @@ -1,11 +1,11 @@ import * as SentryAstro from '@sentry/astro'; +import * as SentryAWS from '@sentry/aws-serverless'; import * as SentryBun from '@sentry/bun'; import * as SentryGoogleCloud from '@sentry/google-cloud'; import * as SentryNextJs from '@sentry/nextjs'; import * as SentryNode from '@sentry/node'; import * as SentryNodeExperimental from '@sentry/node-experimental'; import * as SentryRemix from '@sentry/remix'; -import * as SentryServerless from '@sentry/serverless'; import * as SentrySvelteKit from '@sentry/sveltekit'; /* List of exports that are safe to ignore / we don't require in any depending package */ @@ -77,9 +77,9 @@ const DEPENDENTS: Dependent[] = [ exports: Object.keys(SentryRemix), }, { - package: '@sentry/serverless', - compareWith: nodeExperimentalExports, - exports: Object.keys(SentryServerless), + package: '@sentry/aws-serverless', + compareWith: nodeExports, + exports: Object.keys(SentryAWS), ignoreExports: ['cron', 'hapiErrorPlugin'], }, { diff --git a/dev-packages/e2e-tests/verdaccio-config/config.yaml b/dev-packages/e2e-tests/verdaccio-config/config.yaml index 0e96af866114..00327d6c25c6 100644 --- a/dev-packages/e2e-tests/verdaccio-config/config.yaml +++ b/dev-packages/e2e-tests/verdaccio-config/config.yaml @@ -134,7 +134,7 @@ packages: unpublish: $all # proxy: npmjs # Don't proxy for E2E tests! - '@sentry/serverless': + '@sentry/aws-serverless': access: $all publish: $all unpublish: $all diff --git a/dev-packages/rollup-utils/bundleHelpers.mjs b/dev-packages/rollup-utils/bundleHelpers.mjs index ec3f886abe67..50476c8f9f05 100644 --- a/dev-packages/rollup-utils/bundleHelpers.mjs +++ b/dev-packages/rollup-utils/bundleHelpers.mjs @@ -84,7 +84,7 @@ export function makeBaseBundleConfig(options) { plugins: [rrwebBuildPlugin, markAsBrowserBuildPlugin], }; - // used by `@sentry/serverless`, when creating the lambda layer + // used by `@sentry/aws-serverless`, when creating the lambda layer const nodeBundleConfig = { output: { format: 'cjs', diff --git a/packages/serverless/.eslintrc.js b/packages/aws-serverless/.eslintrc.js similarity index 100% rename from packages/serverless/.eslintrc.js rename to packages/aws-serverless/.eslintrc.js diff --git a/packages/serverless/LICENSE b/packages/aws-serverless/LICENSE similarity index 100% rename from packages/serverless/LICENSE rename to packages/aws-serverless/LICENSE diff --git a/packages/serverless/README.md b/packages/aws-serverless/README.md similarity index 57% rename from packages/serverless/README.md rename to packages/aws-serverless/README.md index aec2e5e27872..d9569d4b5d9a 100644 --- a/packages/serverless/README.md +++ b/packages/aws-serverless/README.md @@ -14,29 +14,29 @@ ## General This package is a wrapper around `@sentry/node`, with added functionality related to various Serverless solutions. All -methods available in `@sentry/node` can be imported from `@sentry/serverless`. +methods available in `@sentry/node` can be imported from `@sentry/aws-serverless`. Currently supported environment: ### AWS Lambda -To use this SDK, call `Sentry.AWSLambda.init(options)` at the very beginning of your JavaScript file. +To use this SDK, call `Sentry.init(options)` at the very beginning of your JavaScript file. ```javascript -import * as Sentry from '@sentry/serverless'; +import * as Sentry from '@sentry/aws-serverless'; -Sentry.AWSLambda.init({ +Sentry.init({ dsn: '__DSN__', // ... }); // async (recommended) -exports.handler = Sentry.AWSLambda.wrapHandler(async (event, context) => { +exports.handler = Sentry.wrapHandler(async (event, context) => { throw new Error('oh, hello there!'); }); // sync -exports.handler = Sentry.AWSLambda.wrapHandler((event, context, callback) => { +exports.handler = Sentry.wrapHandler((event, context, callback) => { throw new Error('oh, hello there!'); }); ``` @@ -44,7 +44,7 @@ exports.handler = Sentry.AWSLambda.wrapHandler((event, context, callback) => { If you also want to trace performance of all the incoming requests and also outgoing AWS service requests, just set the `tracesSampleRate` option. ```javascript -import * as Sentry from '@sentry/serverless'; +import * as Sentry from '@sentry/aws-serverless'; Sentry.AWSLambda.init({ dsn: '__DSN__', @@ -59,38 +59,6 @@ Another and much simpler way to integrate Sentry to your AWS Lambda function is 1. Choose Layers -> Add Layer. 2. Specify an ARN: `arn:aws:lambda:us-west-1:TODO:layer:TODO:VERSION`. 3. Go to Environment variables and add: - - `NODE_OPTIONS`: `-r @sentry/serverless/build/npm/cjs/awslambda-auto`. + - `NODE_OPTIONS`: `-r @sentry/aws-serverless/build/npm/cjs/awslambda-auto`. - `SENTRY_DSN`: `your dsn`. - `SENTRY_TRACES_SAMPLE_RATE`: a number between 0 and 1 representing the chance a transaction is sent to Sentry. For more information, see [docs](https://docs.sentry.io/platforms/node/guides/aws-lambda/configuration/options/#tracesSampleRate). - -### Google Cloud Functions - -To use this SDK, call `Sentry.GCPFunction.init(options)` at the very beginning of your JavaScript file. - -```javascript -import * as Sentry from '@sentry/serverless'; - -Sentry.GCPFunction.init({ - dsn: '__DSN__', - tracesSampleRate: 1.0, - // ... -}); - -// For HTTP Functions: - -exports.helloHttp = Sentry.GCPFunction.wrapHttpFunction((req, res) => { - throw new Error('oh, hello there!'); -}); - -// For Background Functions: - -exports.helloEvents = Sentry.GCPFunction.wrapEventFunction((data, context, callback) => { - throw new Error('oh, hello there!'); -}); - -// For CloudEvents: - -exports.helloEvents = Sentry.GCPFunction.wrapCloudEventFunction((context, callback) => { - throw new Error('oh, hello there!'); -}); -``` diff --git a/packages/serverless/jest.config.js b/packages/aws-serverless/jest.config.js similarity index 100% rename from packages/serverless/jest.config.js rename to packages/aws-serverless/jest.config.js diff --git a/packages/serverless/package.json b/packages/aws-serverless/package.json similarity index 89% rename from packages/serverless/package.json rename to packages/aws-serverless/package.json index e1f83f7bea17..90a90726fd73 100644 --- a/packages/serverless/package.json +++ b/packages/aws-serverless/package.json @@ -1,7 +1,7 @@ { - "name": "@sentry/serverless", + "name": "@sentry/aws-serverless", "version": "8.0.0-alpha.2", - "description": "Official Sentry SDK for various serverless solutions", + "description": "Official Sentry SDK for AWS Lambda and AWS Serverless Environments", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/serverless", "author": "Sentry", @@ -43,21 +43,16 @@ }, "dependencies": { "@sentry/core": "8.0.0-alpha.2", - "@sentry/node-experimental": "8.0.0-alpha.2", + "@sentry/node": "8.0.0-alpha.2", "@sentry/types": "8.0.0-alpha.2", "@sentry/utils": "8.0.0-alpha.2", "@types/aws-lambda": "^8.10.62", "@types/express": "^4.17.14" }, "devDependencies": { - "@google-cloud/bigquery": "^5.3.0", - "@google-cloud/common": "^3.4.1", - "@google-cloud/functions-framework": "^1.7.1", - "@google-cloud/pubsub": "^2.5.0", "@types/node": "^14.6.4", "aws-sdk": "^2.765.0", "find-up": "^5.0.0", - "google-gax": "^2.9.0", "nock": "^13.0.4", "npm-packlist": "^2.1.4" }, diff --git a/packages/serverless/rollup.aws.config.mjs b/packages/aws-serverless/rollup.aws.config.mjs similarity index 88% rename from packages/serverless/rollup.aws.config.mjs rename to packages/aws-serverless/rollup.aws.config.mjs index f50694404dee..dfad060c07fc 100644 --- a/packages/serverless/rollup.aws.config.mjs +++ b/packages/aws-serverless/rollup.aws.config.mjs @@ -7,11 +7,11 @@ export default [ // this automatically sets it to be CJS bundleType: 'node', entrypoints: ['src/index.awslambda.ts'], - licenseTitle: '@sentry/serverless', + licenseTitle: '@sentry/aws-serverless', outputFileBase: () => 'index', packageSpecificConfig: { output: { - dir: 'build/aws/dist-serverless/nodejs/node_modules/@sentry/serverless/build/npm/cjs', + dir: 'build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/cjs', sourcemap: false, }, }, @@ -23,7 +23,7 @@ export default [ ), // This builds a wrapper file, which our lambda layer integration automatically sets up to run as soon as node - // launches (via the `NODE_OPTIONS="-r @sentry/serverless/dist/awslambda-auto"` variable). Note the inclusion in this + // launches (via the `NODE_OPTIONS="-r @sentry/aws-serverless/dist/awslambda-auto"` variable). Note the inclusion in this // path of the legacy `dist` folder; for backwards compatibility, in the build script we'll copy the file there. makeBaseNPMConfig({ entrypoints: ['src/awslambda-auto.ts'], @@ -32,7 +32,7 @@ export default [ // and the directory structure is different than normal, so we have to do it ourselves. output: { format: 'cjs', - dir: 'build/aws/dist-serverless/nodejs/node_modules/@sentry/serverless/build/npm/cjs', + dir: 'build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/cjs', sourcemap: false, }, // We only want `awslambda-auto.js`, not the modules that it imports, because they're all included in the bundle diff --git a/packages/serverless/rollup.npm.config.mjs b/packages/aws-serverless/rollup.npm.config.mjs similarity index 100% rename from packages/serverless/rollup.npm.config.mjs rename to packages/aws-serverless/rollup.npm.config.mjs diff --git a/packages/serverless/scripts/buildLambdaLayer.ts b/packages/aws-serverless/scripts/buildLambdaLayer.ts similarity index 84% rename from packages/serverless/scripts/buildLambdaLayer.ts rename to packages/aws-serverless/scripts/buildLambdaLayer.ts index 3522ff021a33..310cfe606ca0 100644 --- a/packages/serverless/scripts/buildLambdaLayer.ts +++ b/packages/aws-serverless/scripts/buildLambdaLayer.ts @@ -20,27 +20,27 @@ async function buildLambdaLayer(): Promise { // We build a minified bundle, but it's standing in for the regular `index.js` file listed in `package.json`'s `main` // property, so we have to rename it so it's findable. fs.renameSync( - 'build/aws/dist-serverless/nodejs/node_modules/@sentry/serverless/build/npm/cjs/index.min.js', - 'build/aws/dist-serverless/nodejs/node_modules/@sentry/serverless/build/npm/cjs/index.js', + 'build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/cjs/index.min.js', + 'build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/build/npm/cjs/index.js', ); // We're creating a bundle for the SDK, but still using it in a Node context, so we need to copy in `package.json`, // purely for its `main` property. console.log('Copying `package.json` into lambda layer.'); - fs.copyFileSync('package.json', 'build/aws/dist-serverless/nodejs/node_modules/@sentry/serverless/package.json'); + fs.copyFileSync('package.json', 'build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/package.json'); // The layer also includes `awslambda-auto.js`, a helper file which calls `Sentry.init()` and wraps the lambda // handler. It gets run when Node is launched inside the lambda, using the environment variable // - // `NODE_OPTIONS="-r @sentry/serverless/dist/awslambda-auto"`. + // `NODE_OPTIONS="-r @sentry/aws-serverless/dist/awslambda-auto"`. // // (The`-r` is what runs the script on startup.) The `dist` directory is no longer where we emit our built code, so // for backwards compatibility, we create a symlink. console.log('Creating symlink for `awslambda-auto.js` in legacy `dist` directory.'); - fsForceMkdirSync('build/aws/dist-serverless/nodejs/node_modules/@sentry/serverless/dist'); + fsForceMkdirSync('build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/dist'); fs.symlinkSync( '../build/npm/cjs/awslambda-auto.js', - 'build/aws/dist-serverless/nodejs/node_modules/@sentry/serverless/dist/awslambda-auto.js', + 'build/aws/dist-serverless/nodejs/node_modules/@sentry/aws-serverless/dist/awslambda-auto.js', ); const zipFilename = `sentry-node-serverless-${version}.zip`; diff --git a/packages/serverless/src/awslambda-auto.ts b/packages/aws-serverless/src/awslambda-auto.ts similarity index 65% rename from packages/serverless/src/awslambda-auto.ts rename to packages/aws-serverless/src/awslambda-auto.ts index ac048cde5aed..6287b35e8651 100644 --- a/packages/serverless/src/awslambda-auto.ts +++ b/packages/aws-serverless/src/awslambda-auto.ts @@ -1,4 +1,4 @@ -import * as Sentry from './index'; +import { init, tryPatchHandler } from './awslambda'; const lambdaTaskRoot = process.env.LAMBDA_TASK_ROOT; if (lambdaTaskRoot) { @@ -7,11 +7,9 @@ if (lambdaTaskRoot) { throw Error(`LAMBDA_TASK_ROOT is non-empty(${lambdaTaskRoot}) but _HANDLER is not set`); } - Sentry.AWSLambda.init({ - _invokedByLambdaLayer: true, - }); + init(); - Sentry.AWSLambda.tryPatchHandler(lambdaTaskRoot, handlerString); + tryPatchHandler(lambdaTaskRoot, handlerString); } else { throw Error('LAMBDA_TASK_ROOT environment variable is not set'); } diff --git a/packages/serverless/src/awslambda.ts b/packages/aws-serverless/src/awslambda.ts similarity index 95% rename from packages/serverless/src/awslambda.ts rename to packages/aws-serverless/src/awslambda.ts index 5a6fcea389fa..b918494fdb90 100644 --- a/packages/serverless/src/awslambda.ts +++ b/packages/aws-serverless/src/awslambda.ts @@ -2,9 +2,9 @@ import { existsSync } from 'fs'; import { hostname } from 'os'; import { basename, resolve } from 'path'; import { types } from 'util'; -import type { NodeOptions } from '@sentry/node-experimental'; -import { SDK_VERSION } from '@sentry/node-experimental'; +import type { NodeOptions } from '@sentry/node'; import { + SDK_VERSION, captureException, captureMessage, continueTrace, @@ -14,7 +14,7 @@ import { init as initNode, startSpanManual, withScope, -} from '@sentry/node-experimental'; +} from '@sentry/node'; import type { Integration, Options, Scope, SdkMetadata, Span } from '@sentry/types'; import { isString, logger } from '@sentry/utils'; import type { Context, Handler } from 'aws-lambda'; @@ -26,8 +26,6 @@ import { awsServicesIntegration } from './awsservices'; import { DEBUG_BUILD } from './debug-build'; import { markEventUnhandled } from './utils'; -export * from '@sentry/node-experimental'; - const { isPromise } = types; // https://www.npmjs.com/package/aws-lambda-consumer @@ -70,20 +68,12 @@ export function getDefaultIntegrations(options: Options): Integration[] { return [...getNodeDefaultIntegrations(options), awsServicesIntegration({ optional: true })]; } -interface AWSLambdaOptions extends NodeOptions { - /** - * Internal field that is set to `true` when init() is called by the Sentry AWS Lambda layer. - * - */ - _invokedByLambdaLayer?: boolean; -} - /** * Initializes the Sentry AWS Lambda SDK. * * @param options Configuration options for the SDK, @see {@link AWSLambdaOptions}. */ -export function init(options: AWSLambdaOptions = {}): void { +export function init(options: NodeOptions = {}): void { const opts = { _metadata: {} as SdkMetadata, defaultIntegrations: getDefaultIntegrations(options), @@ -91,11 +81,11 @@ export function init(options: AWSLambdaOptions = {}): void { }; opts._metadata.sdk = opts._metadata.sdk || { - name: 'sentry.javascript.serverless', + name: 'sentry.javascript.aws-serverless', integrations: ['AWSLambda'], packages: [ { - name: 'npm:@sentry/serverless', + name: 'npm:@sentry/aws-serverless', version: SDK_VERSION, }, ], diff --git a/packages/serverless/src/awsservices.ts b/packages/aws-serverless/src/awsservices.ts similarity index 86% rename from packages/serverless/src/awsservices.ts rename to packages/aws-serverless/src/awsservices.ts index fb241e0e9638..fa606e89993b 100644 --- a/packages/serverless/src/awsservices.ts +++ b/packages/aws-serverless/src/awsservices.ts @@ -1,6 +1,6 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration } from '@sentry/core'; import { getClient, startInactiveSpan } from '@sentry/node-experimental'; -import type { Client, Integration, IntegrationClass, IntegrationFn, Span } from '@sentry/types'; +import type { Client, IntegrationFn, Span } from '@sentry/types'; import { fill } from '@sentry/utils'; // 'aws-sdk/global' import is expected to be type-only so it's erased in the final .js file. // When TypeScript compiler is upgraded, use `import type` syntax to explicitly assert that we don't want to load a module here. @@ -41,21 +41,10 @@ const _awsServicesIntegration = ((options: { optional?: boolean } = {}) => { }; }) satisfies IntegrationFn; -export const awsServicesIntegration = defineIntegration(_awsServicesIntegration); - /** * AWS Service Request Tracking. - * - * @deprecated Use `awsServicesIntegration()` instead. */ -// eslint-disable-next-line deprecation/deprecation -export const AWSServices = convertIntegrationFnToClass( - INTEGRATION_NAME, - awsServicesIntegration, -) as IntegrationClass; - -// eslint-disable-next-line deprecation/deprecation -export type AWSServices = typeof AWSServices; +export const awsServicesIntegration = defineIntegration(_awsServicesIntegration); /** * Patches AWS SDK request to create `http.client` spans. diff --git a/packages/serverless/src/debug-build.ts b/packages/aws-serverless/src/debug-build.ts similarity index 100% rename from packages/serverless/src/debug-build.ts rename to packages/aws-serverless/src/debug-build.ts diff --git a/packages/serverless/src/index.awslambda.ts b/packages/aws-serverless/src/index.awslambda.ts similarity index 100% rename from packages/serverless/src/index.awslambda.ts rename to packages/aws-serverless/src/index.awslambda.ts diff --git a/packages/serverless/src/index.ts b/packages/aws-serverless/src/index.ts similarity index 60% rename from packages/serverless/src/index.ts rename to packages/aws-serverless/src/index.ts index d160c6fed54b..d439c016faa7 100644 --- a/packages/serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -1,103 +1,94 @@ -// https://medium.com/unsplash/named-namespace-imports-7345212bbffb -import * as AWSLambda from './awslambda'; -import * as GCPFunction from './gcpfunction'; -export { AWSLambda, GCPFunction }; - -// eslint-disable-next-line deprecation/deprecation -export { AWSServices, awsServicesIntegration } from './awsservices'; - -// TODO(v8): We have to explicitly export these because of the namespace exports -// above. This is because just doing `export * from '@sentry/node-experimental'` will not -// work with Node native esm while we also have namespace exports in a package. -// What we should do is get rid of the namespace exports. export { - Hub, - SDK_VERSION, - Scope, - addBreadcrumb, - // eslint-disable-next-line deprecation/deprecation - addGlobalEventProcessor, addEventProcessor, + addBreadcrumb, addIntegration, - autoDiscoverNodePerformanceMonitoringIntegrations, - captureEvent, captureException, + captureEvent, captureMessage, captureCheckIn, + startSession, + captureSession, + endSession, withMonitor, createTransport, // eslint-disable-next-line deprecation/deprecation - getActiveTransaction, - // eslint-disable-next-line deprecation/deprecation getCurrentHub, getClient, isInitialized, getCurrentScope, getGlobalScope, getIsolationScope, - getSpanStatusFromHttpCode, - setHttpStatus, - // eslint-disable-next-line deprecation/deprecation - makeMain, + Hub, setCurrentClient, + Scope, + SDK_VERSION, setContext, setExtra, setExtras, setTag, setTags, setUser, + getSpanStatusFromHttpCode, + setHttpStatus, withScope, withIsolationScope, - NodeClient, makeNodeTransport, - close, - getDefaultIntegrations, + NodeClient, defaultStackParser, flush, + close, getSentryRelease, - init, - DEFAULT_USER_INCLUDES, addRequestDataToEvent, + DEFAULT_USER_INCLUDES, extractRequestData, - Handlers, - // eslint-disable-next-line deprecation/deprecation - Integrations, + createGetModuleFromFilename, + anrIntegration, + consoleIntegration, + httpIntegration, + nativeNodeFetchIntegration, + onUncaughtExceptionIntegration, + onUnhandledRejectionIntegration, + modulesIntegration, + contextLinesIntegration, + nodeContextIntegration, + localVariablesIntegration, + requestDataIntegration, + functionToStringIntegration, + inboundFiltersIntegration, + linkedErrorsIntegration, setMeasurement, getActiveSpan, - getRootSpan, startSpan, startInactiveSpan, startSpanManual, withActiveSpan, + getRootSpan, getSpanDescendants, continueTrace, - parameterize, - requestDataIntegration, - linkedErrorsIntegration, - inboundFiltersIntegration, - functionToStringIntegration, - createGetModuleFromFilename, + getAutoPerformanceIntegrations, + cron, metrics, - consoleIntegration, - onUncaughtExceptionIntegration, - onUnhandledRejectionIntegration, - modulesIntegration, - contextLinesIntegration, - nodeContextIntegration, - localVariablesIntegration, - anrIntegration, - hapiIntegration, - httpIntegration, - nativeNodeFetchintegration, - spotlightIntegration, + parameterize, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, - startSession, - captureSession, - endSession, -} from '@sentry/node-experimental'; + expressIntegration, + expressErrorHandler, + setupExpressErrorHandler, + fastifyIntegration, + graphqlIntegration, + mongoIntegration, + mongooseIntegration, + mysqlIntegration, + mysql2Integration, + nestIntegration, + postgresIntegration, + prismaIntegration, + hapiIntegration, + setupHapiErrorHandler, + spotlightIntegration, +} from '@sentry/node'; export { captureConsoleIntegration, @@ -107,3 +98,8 @@ export { rewriteFramesIntegration, sessionTimingIntegration, } from '@sentry/core'; + +export { getDefaultIntegrations, init, tryPatchHandler, wrapHandler } from './awslambda'; +export type { WrapperOptions } from './awslambda'; + +export { awsServicesIntegration } from './awsservices'; diff --git a/packages/aws-serverless/src/utils.ts b/packages/aws-serverless/src/utils.ts new file mode 100644 index 000000000000..259388bb193c --- /dev/null +++ b/packages/aws-serverless/src/utils.ts @@ -0,0 +1,14 @@ +import type { Scope } from '@sentry/types'; +import { addExceptionMechanism } from '@sentry/utils'; + +/** + * Marks an event as unhandled by adding a span processor to the passed scope. + */ +export function markEventUnhandled(scope: Scope): Scope { + scope.addEventProcessor(event => { + addExceptionMechanism(event, { handled: false }); + return event; + }); + + return scope; +} diff --git a/packages/serverless/test/__mocks__/dns.ts b/packages/aws-serverless/test/__mocks__/dns.ts similarity index 100% rename from packages/serverless/test/__mocks__/dns.ts rename to packages/aws-serverless/test/__mocks__/dns.ts diff --git a/packages/serverless/test/awslambda.test.ts b/packages/aws-serverless/test/awslambda.test.ts similarity index 98% rename from packages/serverless/test/awslambda.test.ts rename to packages/aws-serverless/test/awslambda.test.ts index 964c760f0ca3..bc37f8dfc0a8 100644 --- a/packages/serverless/test/awslambda.test.ts +++ b/packages/aws-serverless/test/awslambda.test.ts @@ -20,8 +20,8 @@ const mockScope = { addEventProcessor: jest.fn(), }; -jest.mock('@sentry/node-experimental', () => { - const original = jest.requireActual('@sentry/node-experimental'); +jest.mock('@sentry/node', () => { + const original = jest.requireActual('@sentry/node'); return { ...original, init: (options: unknown) => { @@ -517,11 +517,11 @@ describe('AWSLambda', () => { expect.objectContaining({ _metadata: { sdk: { - name: 'sentry.javascript.serverless', + name: 'sentry.javascript.aws-serverless', integrations: ['AWSLambda'], packages: [ { - name: 'npm:@sentry/serverless', + name: 'npm:@sentry/aws-serverless', version: expect.any(String), }, ], diff --git a/packages/serverless/test/awsservices.test.ts b/packages/aws-serverless/test/awsservices.test.ts similarity index 97% rename from packages/serverless/test/awsservices.test.ts rename to packages/aws-serverless/test/awsservices.test.ts index c48abb173e16..3170f9056ec0 100644 --- a/packages/serverless/test/awsservices.test.ts +++ b/packages/aws-serverless/test/awsservices.test.ts @@ -1,4 +1,4 @@ -import { NodeClient, createTransport, setCurrentClient } from '@sentry/node-experimental'; +import { NodeClient, createTransport, setCurrentClient } from '@sentry/node'; import * as AWS from 'aws-sdk'; import * as nock from 'nock'; @@ -8,9 +8,9 @@ import { awsServicesIntegration } from '../src/awsservices'; const mockSpanEnd = jest.fn(); const mockStartInactiveSpan = jest.fn(spanArgs => ({ ...spanArgs })); -jest.mock('@sentry/node-experimental', () => { +jest.mock('@sentry/node', () => { return { - ...jest.requireActual('@sentry/node-experimental'), + ...jest.requireActual('@sentry/node'), startInactiveSpan: (ctx: unknown) => { mockStartInactiveSpan(ctx); return { end: mockSpanEnd }; diff --git a/packages/serverless/tsconfig.json b/packages/aws-serverless/tsconfig.json similarity index 100% rename from packages/serverless/tsconfig.json rename to packages/aws-serverless/tsconfig.json diff --git a/packages/serverless/tsconfig.test.json b/packages/aws-serverless/tsconfig.test.json similarity index 100% rename from packages/serverless/tsconfig.test.json rename to packages/aws-serverless/tsconfig.test.json diff --git a/packages/serverless/tsconfig.types.json b/packages/aws-serverless/tsconfig.types.json similarity index 100% rename from packages/serverless/tsconfig.types.json rename to packages/aws-serverless/tsconfig.types.json diff --git a/packages/node/test/index.test.ts b/packages/node/test/index.test.ts index c6fc8a611aee..8379ea34abf1 100644 --- a/packages/node/test/index.test.ts +++ b/packages/node/test/index.test.ts @@ -411,7 +411,7 @@ describe('SentryNode initialization', () => { expect(sdkData.version).toEqual(SDK_VERSION); }); - // wrapper packages (like @sentry/serverless) set their SDK data in their `init` methods, which are + // wrapper packages (like @sentry/aws-serverless) set their SDK data in their `init` methods, which are // called before the client is instantiated, and we don't want to clobber that data it("shouldn't overwrite SDK data that's already there", () => { init({ @@ -419,10 +419,10 @@ describe('SentryNode initialization', () => { // this would normally be set by the wrapper SDK in init() _metadata: { sdk: { - name: 'sentry.javascript.serverless', + name: 'sentry.javascript.aws-serverless', packages: [ { - name: 'npm:@sentry/serverless', + name: 'npm:@sentry/aws-serverless', version: SDK_VERSION, }, ], @@ -433,8 +433,8 @@ describe('SentryNode initialization', () => { const sdkData = getClient()?.getOptions()._metadata?.sdk || {}; - expect(sdkData.name).toEqual('sentry.javascript.serverless'); - expect(sdkData.packages?.[0].name).toEqual('npm:@sentry/serverless'); + expect(sdkData.name).toEqual('sentry.javascript.aws-serverless'); + expect(sdkData.packages?.[0].name).toEqual('npm:@sentry/aws-serverless'); expect(sdkData.packages?.[0].version).toEqual(SDK_VERSION); expect(sdkData.version).toEqual(SDK_VERSION); }); diff --git a/packages/serverless/src/gcpfunction/cloud_events.ts b/packages/serverless/src/gcpfunction/cloud_events.ts deleted file mode 100644 index e24332c19f35..000000000000 --- a/packages/serverless/src/gcpfunction/cloud_events.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, handleCallbackErrors } from '@sentry/core'; -import { captureException, flush, getCurrentScope, startSpanManual } from '@sentry/node-experimental'; -import { logger } from '@sentry/utils'; - -import { DEBUG_BUILD } from '../debug-build'; -import { domainify, markEventUnhandled, proxyFunction } from '../utils'; -import type { CloudEventFunction, CloudEventFunctionWithCallback, WrapperOptions } from './general'; - -export type CloudEventFunctionWrapperOptions = WrapperOptions; - -/** - * Wraps an event function handler adding it error capture and tracing capabilities. - * - * @param fn Event handler - * @param options Options - * @returns Event handler - */ -export function wrapCloudEventFunction( - fn: CloudEventFunction | CloudEventFunctionWithCallback, - wrapOptions: Partial = {}, -): CloudEventFunctionWithCallback { - return proxyFunction(fn, f => domainify(_wrapCloudEventFunction(f, wrapOptions))); -} - -function _wrapCloudEventFunction( - fn: CloudEventFunction | CloudEventFunctionWithCallback, - wrapOptions: Partial = {}, -): CloudEventFunctionWithCallback { - const options: CloudEventFunctionWrapperOptions = { - flushTimeout: 2000, - ...wrapOptions, - }; - return (context, callback) => { - return startSpanManual( - { - name: context.type || '', - op: 'function.gcp.cloud_event', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_cloud_event', - }, - }, - span => { - const scope = getCurrentScope(); - scope.setContext('gcp.function.context', { ...context }); - - const newCallback = domainify((...args: unknown[]) => { - if (args[0] !== null && args[0] !== undefined) { - captureException(args[0], scope => markEventUnhandled(scope)); - } - span.end(); - - // eslint-disable-next-line @typescript-eslint/no-floating-promises - flush(options.flushTimeout) - .then(null, e => { - DEBUG_BUILD && logger.error(e); - }) - .then(() => { - callback(...args); - }); - }); - - if (fn.length > 1) { - return handleCallbackErrors( - () => (fn as CloudEventFunctionWithCallback)(context, newCallback), - err => { - captureException(err, scope => markEventUnhandled(scope)); - }, - ); - } - - return Promise.resolve() - .then(() => (fn as CloudEventFunction)(context)) - .then( - result => newCallback(null, result), - err => newCallback(err, undefined), - ); - }, - ); - }; -} diff --git a/packages/serverless/src/gcpfunction/events.ts b/packages/serverless/src/gcpfunction/events.ts deleted file mode 100644 index 9f6fc6ad2a43..000000000000 --- a/packages/serverless/src/gcpfunction/events.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, handleCallbackErrors } from '@sentry/core'; -import { captureException, flush, getCurrentScope, startSpanManual } from '@sentry/node-experimental'; -import { logger } from '@sentry/utils'; - -import { DEBUG_BUILD } from '../debug-build'; -import { domainify, markEventUnhandled, proxyFunction } from '../utils'; -import type { EventFunction, EventFunctionWithCallback, WrapperOptions } from './general'; - -export type EventFunctionWrapperOptions = WrapperOptions; - -/** - * Wraps an event function handler adding it error capture and tracing capabilities. - * - * @param fn Event handler - * @param options Options - * @returns Event handler - */ -export function wrapEventFunction( - fn: EventFunction | EventFunctionWithCallback, - wrapOptions: Partial = {}, -): EventFunctionWithCallback { - return proxyFunction(fn, f => domainify(_wrapEventFunction(f, wrapOptions))); -} - -/** */ -function _wrapEventFunction( - fn: F, - wrapOptions: Partial = {}, -): (...args: Parameters) => ReturnType | Promise { - const options: EventFunctionWrapperOptions = { - flushTimeout: 2000, - ...wrapOptions, - }; - return (...eventFunctionArguments: Parameters): ReturnType | Promise => { - const [data, context, callback] = eventFunctionArguments; - - return startSpanManual( - { - name: context.eventType, - op: 'function.gcp.event', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_event', - }, - }, - span => { - const scope = getCurrentScope(); - scope.setContext('gcp.function.context', { ...context }); - - const newCallback = domainify((...args: unknown[]) => { - if (args[0] !== null && args[0] !== undefined) { - captureException(args[0], scope => markEventUnhandled(scope)); - } - span.end(); - - // eslint-disable-next-line @typescript-eslint/no-floating-promises - flush(options.flushTimeout) - .then(null, e => { - DEBUG_BUILD && logger.error(e); - }) - .then(() => { - if (typeof callback === 'function') { - callback(...args); - } - }); - }); - - if (fn.length > 2) { - return handleCallbackErrors( - () => (fn as EventFunctionWithCallback)(data, context, newCallback), - err => { - captureException(err, scope => markEventUnhandled(scope)); - }, - ); - } - - return Promise.resolve() - .then(() => (fn as EventFunction)(data, context)) - .then( - result => newCallback(null, result), - err => newCallback(err, undefined), - ); - }, - ); - }; -} diff --git a/packages/serverless/src/gcpfunction/general.ts b/packages/serverless/src/gcpfunction/general.ts deleted file mode 100644 index f819bd5aaaf3..000000000000 --- a/packages/serverless/src/gcpfunction/general.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Request, Response } from 'express'; - -export interface HttpFunction { - (req: Request, res: Response): any; // eslint-disable-line @typescript-eslint/no-explicit-any -} - -export interface EventFunction { - (data: Record, context: Context): any; // eslint-disable-line @typescript-eslint/no-explicit-any -} - -export interface EventFunctionWithCallback { - (data: Record, context: Context, callback: Function): any; // eslint-disable-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types -} - -export interface CloudEventFunction { - (cloudevent: CloudEventsContext): any; // eslint-disable-line @typescript-eslint/no-explicit-any -} - -export interface CloudEventFunctionWithCallback { - (cloudevent: CloudEventsContext, callback: Function): any; // eslint-disable-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types -} - -export interface CloudFunctionsContext { - eventId?: string; - timestamp?: string; - eventType?: string; - resource?: string; -} - -export interface CloudEventsContext { - [key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any - type?: string; - specversion?: string; - source?: string; - id?: string; - time?: string; - schemaurl?: string; - contenttype?: string; -} - -export type Context = CloudFunctionsContext | CloudEventsContext; - -export interface WrapperOptions { - flushTimeout: number; -} - -export type { Request, Response }; diff --git a/packages/serverless/src/gcpfunction/http.ts b/packages/serverless/src/gcpfunction/http.ts deleted file mode 100644 index 724fa2f4faf3..000000000000 --- a/packages/serverless/src/gcpfunction/http.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - Transaction, - handleCallbackErrors, - setHttpStatus, -} from '@sentry/core'; -import { continueTrace, startSpanManual } from '@sentry/node-experimental'; -import { getCurrentScope } from '@sentry/node-experimental'; -import { captureException, flush } from '@sentry/node-experimental'; -import { isString, logger, stripUrlQueryAndFragment } from '@sentry/utils'; - -import { DEBUG_BUILD } from '../debug-build'; -import { domainify, markEventUnhandled, proxyFunction } from './../utils'; -import type { HttpFunction, WrapperOptions } from './general'; - -/** - * Wraps an HTTP function handler adding it error capture and tracing capabilities. - * - * @param fn HTTP Handler - * @param options Options - * @returns HTTP handler - */ -export function wrapHttpFunction(fn: HttpFunction, wrapOptions: Partial = {}): HttpFunction { - const wrap = (f: HttpFunction): HttpFunction => domainify(_wrapHttpFunction(f, wrapOptions)); - - let overrides: Record | undefined; - - // Functions emulator from firebase-tools has a hack-ish workaround that saves the actual function - // passed to `onRequest(...)` and in fact runs it so we need to wrap it too. - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access - const emulatorFunc = (fn as any).__emulator_func as HttpFunction | undefined; - if (emulatorFunc) { - overrides = { __emulator_func: proxyFunction(emulatorFunc, wrap) }; - } - return proxyFunction(fn, wrap, overrides); -} - -/** */ -function _wrapHttpFunction(fn: HttpFunction, options: Partial): HttpFunction { - const flushTimeout = options.flushTimeout || 2000; - return (req, res) => { - const reqMethod = (req.method || '').toUpperCase(); - const reqUrl = stripUrlQueryAndFragment(req.originalUrl || req.url || ''); - - const sentryTrace = req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined; - const baggage = req.headers?.baggage; - - return continueTrace({ sentryTrace, baggage }, () => { - return startSpanManual( - { - name: `${reqMethod} ${reqUrl}`, - op: 'function.gcp.http', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_http', - }, - }, - span => { - getCurrentScope().setSDKProcessingMetadata({ - request: req, - }); - - if (span instanceof Transaction) { - // We also set __sentry_transaction on the response so people can grab the transaction there to add - // spans to it later. - // TODO(v8): Remove this - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access - (res as any).__sentry_transaction = span; - } - - // eslint-disable-next-line @typescript-eslint/unbound-method - const _end = res.end; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - res.end = function (chunk?: any | (() => void), encoding?: string | (() => void), cb?: () => void): any { - setHttpStatus(span, res.statusCode); - span.end(); - - // eslint-disable-next-line @typescript-eslint/no-floating-promises - flush(flushTimeout) - .then(null, e => { - DEBUG_BUILD && logger.error(e); - }) - .then(() => { - _end.call(this, chunk, encoding, cb); - }); - }; - - return handleCallbackErrors( - () => fn(req, res), - err => { - captureException(err, scope => markEventUnhandled(scope)); - }, - ); - }, - ); - }); - }; -} diff --git a/packages/serverless/src/gcpfunction/index.ts b/packages/serverless/src/gcpfunction/index.ts deleted file mode 100644 index ad415b2fb761..000000000000 --- a/packages/serverless/src/gcpfunction/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { NodeOptions } from '@sentry/node-experimental'; -import { - SDK_VERSION, - getDefaultIntegrations as getDefaultNodeIntegrations, - init as initNode, -} from '@sentry/node-experimental'; -import type { Integration, Options, SdkMetadata } from '@sentry/types'; - -import { googleCloudGrpcIntegration } from '../google-cloud-grpc'; -import { googleCloudHttpIntegration } from '../google-cloud-http'; - -export * from './http'; -export * from './events'; -export * from './cloud_events'; - -/** Get the default integrations for the GCP SDK. */ -export function getDefaultIntegrations(options: Options): Integration[] { - return [ - ...getDefaultNodeIntegrations(options), - googleCloudHttpIntegration({ optional: true }), // We mark this integration optional since '@google-cloud/common' module could be missing. - googleCloudGrpcIntegration({ optional: true }), // We mark this integration optional since 'google-gax' module could be missing. - ]; -} - -/** - * @see {@link Sentry.init} - */ -export function init(options: NodeOptions = {}): void { - const opts = { - _metadata: {} as SdkMetadata, - defaultIntegrations: getDefaultIntegrations(options), - ...options, - }; - - opts._metadata.sdk = opts._metadata.sdk || { - name: 'sentry.javascript.serverless', - integrations: ['GCPFunction'], - packages: [ - { - name: 'npm:@sentry/serverless', - version: SDK_VERSION, - }, - ], - version: SDK_VERSION, - }; - - initNode(opts); -} diff --git a/packages/serverless/src/google-cloud-grpc.ts b/packages/serverless/src/google-cloud-grpc.ts deleted file mode 100644 index e982563fb36e..000000000000 --- a/packages/serverless/src/google-cloud-grpc.ts +++ /dev/null @@ -1,145 +0,0 @@ -import type { EventEmitter } from 'events'; -import { - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - convertIntegrationFnToClass, - defineIntegration, - getClient, -} from '@sentry/core'; -import { startInactiveSpan } from '@sentry/node-experimental'; -import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; -import { fill } from '@sentry/utils'; - -interface GrpcFunction extends CallableFunction { - (...args: unknown[]): EventEmitter; -} - -interface GrpcFunctionObject extends GrpcFunction { - requestStream: boolean; - responseStream: boolean; - originalName: string; -} - -interface StubOptions { - servicePath?: string; -} - -interface CreateStubFunc extends CallableFunction { - (createStub: unknown, options: StubOptions): PromiseLike; -} - -interface Stub { - [key: string]: GrpcFunctionObject; -} - -const SERVICE_PATH_REGEX = /^(\w+)\.googleapis.com$/; - -const INTEGRATION_NAME = 'GoogleCloudGrpc'; - -const SETUP_CLIENTS = new WeakMap(); - -const _googleCloudGrpcIntegration = ((options: { optional?: boolean } = {}) => { - const optional = options.optional || false; - return { - name: INTEGRATION_NAME, - setupOnce() { - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const gaxModule = require('google-gax'); - fill( - gaxModule.GrpcClient.prototype, // eslint-disable-line @typescript-eslint/no-unsafe-member-access - 'createStub', - wrapCreateStub, - ); - } catch (e) { - if (!optional) { - throw e; - } - } - }, - setup(client) { - SETUP_CLIENTS.set(client, true); - }, - }; -}) satisfies IntegrationFn; - -export const googleCloudGrpcIntegration = defineIntegration(_googleCloudGrpcIntegration); - -/** - * Google Cloud Platform service requests tracking for GRPC APIs. - * - * @deprecated Use `googleCloudGrpcIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const GoogleCloudGrpc = convertIntegrationFnToClass( - INTEGRATION_NAME, - googleCloudGrpcIntegration, -) as IntegrationClass; - -// eslint-disable-next-line deprecation/deprecation -export type GoogleCloudGrpc = typeof GoogleCloudGrpc; - -/** Returns a wrapped function that returns a stub with tracing enabled */ -function wrapCreateStub(origCreate: CreateStubFunc): CreateStubFunc { - return async function (this: unknown, ...args: Parameters) { - const servicePath = args[1]?.servicePath; - if (servicePath == null || servicePath == undefined) { - return origCreate.apply(this, args); - } - const serviceIdentifier = identifyService(servicePath); - const stub = await origCreate.apply(this, args); - for (const methodName of Object.keys(Object.getPrototypeOf(stub))) { - fillGrpcFunction(stub, serviceIdentifier, methodName); - } - return stub; - }; -} - -/** Patches the function in grpc stub to enable tracing */ -function fillGrpcFunction(stub: Stub, serviceIdentifier: string, methodName: string): void { - const funcObj = stub[methodName]; - if (typeof funcObj !== 'function') { - return; - } - const callType = - !funcObj.requestStream && !funcObj.responseStream - ? 'unary call' - : funcObj.requestStream && !funcObj.responseStream - ? 'client stream' - : !funcObj.requestStream && funcObj.responseStream - ? 'server stream' - : 'bidi stream'; - if (callType != 'unary call') { - return; - } - fill( - stub, - methodName, - (orig: GrpcFunction): GrpcFunction => - (...args) => { - const ret = orig.apply(stub, args); - if (typeof ret?.on !== 'function' || !SETUP_CLIENTS.has(getClient() as Client)) { - return ret; - } - const span = startInactiveSpan({ - name: `${callType} ${methodName}`, - onlyIfParent: true, - op: `grpc.${serviceIdentifier}`, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.grpc.serverless', - }, - }); - ret.on('status', () => { - if (span) { - span.end(); - } - }); - return ret; - }, - ); -} - -/** Identifies service by its address */ -function identifyService(servicePath: string): string { - const match = servicePath.match(SERVICE_PATH_REGEX); - return match ? match[1] : servicePath; -} diff --git a/packages/serverless/src/google-cloud-http.ts b/packages/serverless/src/google-cloud-http.ts deleted file mode 100644 index 9c50cccab5a1..000000000000 --- a/packages/serverless/src/google-cloud-http.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type * as common from '@google-cloud/common'; -import { - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SentryNonRecordingSpan, - convertIntegrationFnToClass, - defineIntegration, - getClient, -} from '@sentry/core'; -import { startInactiveSpan } from '@sentry/node-experimental'; -import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; -import { fill } from '@sentry/utils'; - -type RequestOptions = common.DecorateRequestOptions; -type ResponseCallback = common.BodyResponseCallback; -// This interace could be replaced with just type alias once the `strictBindCallApply` mode is enabled. -interface RequestFunction extends CallableFunction { - (reqOpts: RequestOptions, callback: ResponseCallback): void; -} - -const INTEGRATION_NAME = 'GoogleCloudHttp'; - -const SETUP_CLIENTS = new WeakMap(); - -const _googleCloudHttpIntegration = ((options: { optional?: boolean } = {}) => { - const optional = options.optional || false; - return { - name: INTEGRATION_NAME, - setupOnce() { - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const commonModule = require('@google-cloud/common') as typeof common; - fill(commonModule.Service.prototype, 'request', wrapRequestFunction); - } catch (e) { - if (!optional) { - throw e; - } - } - }, - setup(client) { - SETUP_CLIENTS.set(client, true); - }, - }; -}) satisfies IntegrationFn; - -export const googleCloudHttpIntegration = defineIntegration(_googleCloudHttpIntegration); - -/** - * Google Cloud Platform service requests tracking for RESTful APIs. - * - * @deprecated Use `googleCloudHttpIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const GoogleCloudHttp = convertIntegrationFnToClass( - INTEGRATION_NAME, - googleCloudHttpIntegration, -) as IntegrationClass; - -// eslint-disable-next-line deprecation/deprecation -export type GoogleCloudHttp = typeof GoogleCloudHttp; - -/** Returns a wrapped function that makes a request with tracing enabled */ -function wrapRequestFunction(orig: RequestFunction): RequestFunction { - return function (this: common.Service, reqOpts: RequestOptions, callback: ResponseCallback): void { - const httpMethod = reqOpts.method || 'GET'; - const span = SETUP_CLIENTS.has(getClient() as Client) - ? startInactiveSpan({ - name: `${httpMethod} ${reqOpts.uri}`, - onlyIfParent: true, - op: `http.client.${identifyService(this.apiEndpoint)}`, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.serverless', - }, - }) - : new SentryNonRecordingSpan(); - orig.call(this, reqOpts, (...args: Parameters) => { - span.end(); - callback(...args); - }); - }; -} - -/** Identifies service by its base url */ -function identifyService(apiEndpoint: string): string { - const match = apiEndpoint.match(/^https:\/\/(\w+)\.googleapis.com$/); - return match ? match[1] : apiEndpoint.replace(/^(http|https)?:\/\//, ''); -} diff --git a/packages/serverless/src/utils.ts b/packages/serverless/src/utils.ts deleted file mode 100644 index ba3082b7e262..000000000000 --- a/packages/serverless/src/utils.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { withIsolationScope } from '@sentry/core'; -import type { Scope } from '@sentry/types'; -import { addExceptionMechanism } from '@sentry/utils'; - -/** - * @param fn function to run - * @returns function which runs in the newly created domain or in the existing one - */ -export function domainify(fn: (...args: A) => R): (...args: A) => R | void { - return (...args) => withIsolationScope(() => fn(...args)); -} - -/** - * @param source function to be wrapped - * @param wrap wrapping function that takes source and returns a wrapper - * @param overrides properties to override in the source - * @returns wrapped function - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function proxyFunction R>( - source: F, - wrap: (source: F) => F, - overrides?: Record, -): F { - const wrapper = wrap(source); - const handler: ProxyHandler = { - apply: (_target: F, thisArg: T, args: A) => { - return wrapper.apply(thisArg, args); - }, - }; - - if (overrides) { - handler.get = (target, prop) => { - if (Object.prototype.hasOwnProperty.call(overrides, prop)) { - return overrides[prop as string]; - } - return (target as Record)[prop as string]; - }; - } - - return new Proxy(source, handler); -} - -/** - * Marks an event as unhandled by adding a span processor to the passed scope. - */ -export function markEventUnhandled(scope: Scope): Scope { - scope.addEventProcessor(event => { - addExceptionMechanism(event, { handled: false }); - return event; - }); - - return scope; -} diff --git a/packages/serverless/test/gcpfunction.test.ts b/packages/serverless/test/gcpfunction.test.ts deleted file mode 100644 index c9566200a46d..000000000000 --- a/packages/serverless/test/gcpfunction.test.ts +++ /dev/null @@ -1,595 +0,0 @@ -import * as domain from 'domain'; - -import type { Event, Integration } from '@sentry/types'; - -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; - -import { wrapCloudEventFunction, wrapEventFunction, wrapHttpFunction } from '../src/gcpfunction'; -import type { - CloudEventFunction, - CloudEventFunctionWithCallback, - EventFunction, - EventFunctionWithCallback, - HttpFunction, - Request, - Response, -} from '../src/gcpfunction/general'; - -import { init } from '../src/gcpfunction'; - -const mockStartInactiveSpan = jest.fn((...spanArgs) => ({ ...spanArgs })); -const mockStartSpanManual = jest.fn((...spanArgs) => ({ ...spanArgs })); -const mockFlush = jest.fn((...args) => Promise.resolve(args)); -const mockWithScope = jest.fn(); -const mockCaptureMessage = jest.fn(); -const mockCaptureException = jest.fn(); -const mockInit = jest.fn(); - -const mockScope = { - setTag: jest.fn(), - setContext: jest.fn(), - addEventProcessor: jest.fn(), - setSDKProcessingMetadata: jest.fn(), -}; - -const mockSpan = { - end: jest.fn(), -}; - -jest.mock('@sentry/node-experimental', () => { - const original = jest.requireActual('@sentry/node-experimental'); - return { - ...original, - init: (options: unknown) => { - mockInit(options); - }, - startInactiveSpan: (...args: unknown[]) => { - mockStartInactiveSpan(...args); - return mockSpan; - }, - startSpanManual: (...args: unknown[]) => { - mockStartSpanManual(...args); - mockSpan.end(); - return original.startSpanManual(...args); - }, - getCurrentScope: () => { - return mockScope; - }, - flush: (...args: unknown[]) => { - return mockFlush(...args); - }, - withScope: (fn: (scope: unknown) => void) => { - mockWithScope(fn); - fn(mockScope); - }, - captureMessage: (...args: unknown[]) => { - mockCaptureMessage(...args); - }, - captureException: (...args: unknown[]) => { - mockCaptureException(...args); - }, - }; -}); - -describe('GCPFunction', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - async function handleHttp(fn: HttpFunction, trace_headers: { [key: string]: string } | null = null): Promise { - let headers: { [key: string]: string } = { host: 'hostname', 'content-type': 'application/json' }; - if (trace_headers) { - headers = { ...headers, ...trace_headers }; - } - return new Promise((resolve, _reject) => { - const d = domain.create(); - const req = { - method: 'POST', - url: '/path?q=query', - headers: headers, - body: { foo: 'bar' }, - } as Request; - const res = { end: resolve } as Response; - d.on('error', () => res.end()); - d.run(() => process.nextTick(fn, req, res)); - }); - } - - function handleEvent(fn: EventFunctionWithCallback): Promise { - return new Promise((resolve, reject) => { - const d = domain.create(); - // d.on('error', () => res.end()); - const context = { - eventType: 'event.type', - resource: 'some.resource', - }; - d.on('error', reject); - d.run(() => - process.nextTick(fn, {}, context, (err: any, result: any) => { - if (err != null || err != undefined) { - reject(err); - } else { - resolve(result); - } - }), - ); - }); - } - - function handleCloudEvent(fn: CloudEventFunctionWithCallback): Promise { - return new Promise((resolve, reject) => { - const d = domain.create(); - // d.on('error', () => res.end()); - const context = { - type: 'event.type', - }; - d.on('error', reject); - d.run(() => - process.nextTick(fn, context, (err: any, result: any) => { - if (err != null || err != undefined) { - reject(err); - } else { - resolve(result); - } - }), - ); - }); - } - - describe('wrapHttpFunction() options', () => { - test('flushTimeout', async () => { - const handler: HttpFunction = (_, res) => { - res.end(); - }; - const wrappedHandler = wrapHttpFunction(handler, { flushTimeout: 1337 }); - - await handleHttp(wrappedHandler); - expect(mockFlush).toBeCalledWith(1337); - }); - }); - - describe('wrapHttpFunction()', () => { - test('successful execution', async () => { - const handler: HttpFunction = (_req, res) => { - res.statusCode = 200; - res.end(); - }; - const wrappedHandler = wrapHttpFunction(handler); - await handleHttp(wrappedHandler); - - const fakeTransactionContext = { - name: 'POST /path', - op: 'function.gcp.http', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_http', - }, - }; - - expect(mockStartSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); - expect(mockSpan.end).toBeCalled(); - expect(mockFlush).toBeCalledWith(2000); - }); - - test('capture error', async () => { - const error = new Error('wat'); - const handler: HttpFunction = (_req, _res) => { - throw error; - }; - const wrappedHandler = wrapHttpFunction(handler); - - await handleHttp(wrappedHandler); - - const fakeTransactionContext = { - name: 'POST /path', - op: 'function.gcp.http', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_http', - }, - }; - - expect(mockStartSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); - expect(mockCaptureException).toBeCalledWith(error, expect.any(Function)); - expect(mockSpan.end).toBeCalled(); - expect(mockFlush).toBeCalled(); - }); - - test('should not throw when flush rejects', async () => { - const handler: HttpFunction = async (_req, res) => { - res.statusCode = 200; - res.end(); - }; - - const wrappedHandler = wrapHttpFunction(handler); - - const request = { - method: 'POST', - url: '/path?q=query', - headers: { host: 'hostname', 'content-type': 'application/json' }, - body: { foo: 'bar' }, - } as Request; - - const mockEnd = jest.fn(); - const response = { end: mockEnd } as unknown as Response; - - mockFlush.mockImplementationOnce(async () => { - throw new Error(); - }); - - await expect(wrappedHandler(request, response)).resolves.toBeUndefined(); - expect(mockEnd).toHaveBeenCalledTimes(1); - }); - }); - - // This tests that the necessary pieces are in place for request data to get added to event - the `RequestData` - // integration is included in the defaults and the necessary data is stored in `sdkProcessingMetadata`. The - // integration's tests cover testing that it uses that data correctly. - test('wrapHttpFunction request data prereqs', async () => { - init({}); - - const handler: HttpFunction = (_req, res) => { - res.end(); - }; - const wrappedHandler = wrapHttpFunction(handler); - - await handleHttp(wrappedHandler); - - const initOptions = (mockInit as unknown as jest.SpyInstance).mock.calls[0]; - const defaultIntegrations = initOptions[0].defaultIntegrations.map((i: Integration) => i.name); - - expect(defaultIntegrations).toContain('RequestData'); - - expect(mockScope.setSDKProcessingMetadata).toHaveBeenCalledWith({ - request: { - method: 'POST', - url: '/path?q=query', - headers: { host: 'hostname', 'content-type': 'application/json' }, - body: { foo: 'bar' }, - }, - }); - }); - - describe('wrapEventFunction() without callback', () => { - test('successful execution', async () => { - const func: EventFunction = (_data, _context) => { - return 42; - }; - const wrappedHandler = wrapEventFunction(func); - await expect(handleEvent(wrappedHandler)).resolves.toBe(42); - - const fakeTransactionContext = { - name: 'event.type', - op: 'function.gcp.event', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_event', - }, - }; - - expect(mockStartSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); - expect(mockSpan.end).toBeCalled(); - expect(mockFlush).toBeCalledWith(2000); - }); - - test('capture error', async () => { - const error = new Error('wat'); - const handler: EventFunction = (_data, _context) => { - throw error; - }; - const wrappedHandler = wrapEventFunction(handler); - await expect(handleEvent(wrappedHandler)).rejects.toThrowError(error); - - const fakeTransactionContext = { - name: 'event.type', - op: 'function.gcp.event', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_event', - }, - }; - - expect(mockStartSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); - expect(mockCaptureException).toBeCalledWith(error, expect.any(Function)); - expect(mockSpan.end).toBeCalled(); - expect(mockFlush).toBeCalled(); - }); - }); - - describe('wrapEventFunction() as Promise', () => { - test('successful execution', async () => { - const func: EventFunction = (_data, _context) => - new Promise(resolve => { - setTimeout(() => { - resolve(42); - }, 10); - }); - const wrappedHandler = wrapEventFunction(func); - await expect(handleEvent(wrappedHandler)).resolves.toBe(42); - - const fakeTransactionContext = { - name: 'event.type', - op: 'function.gcp.event', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_event', - }, - }; - - expect(mockStartSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); - expect(mockSpan.end).toBeCalled(); - expect(mockFlush).toBeCalledWith(2000); - }); - - test('capture error', async () => { - const error = new Error('wat'); - const handler: EventFunction = (_data, _context) => - new Promise((_, reject) => { - setTimeout(() => { - reject(error); - }, 10); - }); - - const wrappedHandler = wrapEventFunction(handler); - await expect(handleEvent(wrappedHandler)).rejects.toThrowError(error); - - const fakeTransactionContext = { - name: 'event.type', - op: 'function.gcp.event', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_event', - }, - }; - - expect(mockStartSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); - expect(mockCaptureException).toBeCalledWith(error, expect.any(Function)); - expect(mockSpan.end).toBeCalled(); - expect(mockFlush).toBeCalled(); - }); - }); - - describe('wrapEventFunction() with callback', () => { - test('successful execution', async () => { - const func: EventFunctionWithCallback = (_data, _context, cb) => { - cb(null, 42); - }; - const wrappedHandler = wrapEventFunction(func); - await expect(handleEvent(wrappedHandler)).resolves.toBe(42); - - const fakeTransactionContext = { - name: 'event.type', - op: 'function.gcp.event', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_event', - }, - }; - - expect(mockStartSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); - expect(mockSpan.end).toBeCalled(); - expect(mockFlush).toBeCalledWith(2000); - }); - - test('capture error', async () => { - const error = new Error('wat'); - const handler: EventFunctionWithCallback = (_data, _context, cb) => { - cb(error); - }; - const wrappedHandler = wrapEventFunction(handler); - await expect(handleEvent(wrappedHandler)).rejects.toThrowError(error); - - const fakeTransactionContext = { - name: 'event.type', - op: 'function.gcp.event', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_event', - }, - }; - - expect(mockStartSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); - expect(mockCaptureException).toBeCalledWith(error, expect.any(Function)); - expect(mockSpan.end).toBeCalled(); - expect(mockFlush).toBeCalled(); - }); - - test('capture exception', async () => { - const error = new Error('wat'); - const handler: EventFunctionWithCallback = (_data, _context, _cb) => { - throw error; - }; - const wrappedHandler = wrapEventFunction(handler); - await expect(handleEvent(wrappedHandler)).rejects.toThrowError(error); - - const fakeTransactionContext = { - name: 'event.type', - op: 'function.gcp.event', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_event', - }, - }; - - expect(mockStartSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); - expect(mockCaptureException).toBeCalledWith(error, expect.any(Function)); - }); - }); - - test('marks the captured error as unhandled', async () => { - const error = new Error('wat'); - const handler: EventFunctionWithCallback = (_data, _context, _cb) => { - throw error; - }; - const wrappedHandler = wrapEventFunction(handler); - await expect(handleEvent(wrappedHandler)).rejects.toThrowError(error); - - expect(mockCaptureException).toBeCalledWith(error, expect.any(Function)); - - const scopeFunction = mockCaptureException.mock.calls[0][1]; - const event: Event = { exception: { values: [{}] } }; - let evtProcessor: ((e: Event) => Event) | undefined = undefined; - scopeFunction({ addEventProcessor: jest.fn().mockImplementation(proc => (evtProcessor = proc)) }); - - expect(evtProcessor).toBeInstanceOf(Function); - // @ts-expect-error just mocking around... - expect(evtProcessor(event).exception.values[0].mechanism).toEqual({ - handled: false, - type: 'generic', - }); - }); - - test('wrapEventFunction scope data', async () => { - const handler: EventFunction = (_data, _context) => 42; - const wrappedHandler = wrapEventFunction(handler); - await handleEvent(wrappedHandler); - expect(mockScope.setContext).toBeCalledWith('gcp.function.context', { - eventType: 'event.type', - resource: 'some.resource', - }); - }); - - describe('wrapCloudEventFunction() without callback', () => { - test('successful execution', async () => { - const func: CloudEventFunction = _context => { - return 42; - }; - const wrappedHandler = wrapCloudEventFunction(func); - await expect(handleCloudEvent(wrappedHandler)).resolves.toBe(42); - - const fakeTransactionContext = { - name: 'event.type', - op: 'function.gcp.cloud_event', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_cloud_event', - }, - }; - - expect(mockStartSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); - expect(mockSpan.end).toBeCalled(); - expect(mockFlush).toBeCalledWith(2000); - }); - - test('capture error', async () => { - const error = new Error('wat'); - const handler: CloudEventFunction = _context => { - throw error; - }; - const wrappedHandler = wrapCloudEventFunction(handler); - await expect(handleCloudEvent(wrappedHandler)).rejects.toThrowError(error); - - const fakeTransactionContext = { - name: 'event.type', - op: 'function.gcp.cloud_event', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_cloud_event', - }, - }; - - expect(mockStartSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); - expect(mockCaptureException).toBeCalledWith(error, expect.any(Function)); - expect(mockSpan.end).toBeCalled(); - expect(mockFlush).toBeCalled(); - }); - }); - - describe('wrapCloudEventFunction() with callback', () => { - test('successful execution', async () => { - const func: CloudEventFunctionWithCallback = (_context, cb) => { - cb(null, 42); - }; - const wrappedHandler = wrapCloudEventFunction(func); - await expect(handleCloudEvent(wrappedHandler)).resolves.toBe(42); - - const fakeTransactionContext = { - name: 'event.type', - op: 'function.gcp.cloud_event', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_cloud_event', - }, - }; - - expect(mockStartSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); - expect(mockSpan.end).toBeCalled(); - expect(mockFlush).toBeCalledWith(2000); - }); - - test('capture error', async () => { - const error = new Error('wat'); - const handler: CloudEventFunctionWithCallback = (_context, cb) => { - cb(error); - }; - const wrappedHandler = wrapCloudEventFunction(handler); - await expect(handleCloudEvent(wrappedHandler)).rejects.toThrowError(error); - - const fakeTransactionContext = { - name: 'event.type', - op: 'function.gcp.cloud_event', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_cloud_event', - }, - }; - - expect(mockStartSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); - expect(mockCaptureException).toBeCalledWith(error, expect.any(Function)); - expect(mockSpan.end).toBeCalled(); - expect(mockFlush).toBeCalled(); - }); - - test('capture exception', async () => { - const error = new Error('wat'); - const handler: CloudEventFunctionWithCallback = (_context, _cb) => { - throw error; - }; - const wrappedHandler = wrapCloudEventFunction(handler); - await expect(handleCloudEvent(wrappedHandler)).rejects.toThrowError(error); - - const fakeTransactionContext = { - name: 'event.type', - op: 'function.gcp.cloud_event', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.serverless.gcp_cloud_event', - }, - }; - - expect(mockStartSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); - expect(mockCaptureException).toBeCalledWith(error, expect.any(Function)); - }); - }); - - test('wrapCloudEventFunction scope data', async () => { - const handler: CloudEventFunction = _context => 42; - const wrappedHandler = wrapCloudEventFunction(handler); - await handleCloudEvent(wrappedHandler); - expect(mockScope.setContext).toBeCalledWith('gcp.function.context', { type: 'event.type' }); - }); - - describe('init()', () => { - test('calls Sentry.init with correct sdk info metadata', () => { - init({}); - - expect(mockInit).toBeCalledWith( - expect.objectContaining({ - _metadata: { - sdk: { - name: 'sentry.javascript.serverless', - integrations: ['GCPFunction'], - packages: [ - { - name: 'npm:@sentry/serverless', - version: expect.any(String), - }, - ], - version: expect.any(String), - }, - }, - }), - ); - }); - }); -}); diff --git a/packages/serverless/test/google-cloud-grpc.test.ts b/packages/serverless/test/google-cloud-grpc.test.ts deleted file mode 100644 index d9d789d33480..000000000000 --- a/packages/serverless/test/google-cloud-grpc.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -jest.mock('dns'); - -import * as dns from 'dns'; -import { EventEmitter } from 'events'; -import * as fs from 'fs'; -import * as path from 'path'; -import { PubSub } from '@google-cloud/pubsub'; -import * as http2 from 'http2'; -import * as nock from 'nock'; - -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; -import { NodeClient, createTransport, setCurrentClient } from '@sentry/node-experimental'; -import { googleCloudGrpcIntegration } from '../src/google-cloud-grpc'; - -const spyConnect = jest.spyOn(http2, 'connect'); - -const mockSpanEnd = jest.fn(); -const mockStartInactiveSpan = jest.fn(spanArgs => ({ ...spanArgs })); - -jest.mock('@sentry/node-experimental', () => { - return { - ...jest.requireActual('@sentry/node-experimental'), - startInactiveSpan: (ctx: unknown) => { - mockStartInactiveSpan(ctx); - return { end: mockSpanEnd }; - }, - }; -}); - -/** Fake HTTP2 stream */ -class FakeStream extends EventEmitter { - public rstCode: number = 0; - close() { - this.emit('end'); - this.emit('close'); - } - end() {} - pause() {} - resume() {} - write(_data: Buffer, cb: CallableFunction) { - process.nextTick(cb, null); - } -} - -/** Fake HTTP2 session for GRPC */ -class FakeSession extends EventEmitter { - public socket: EventEmitter = new EventEmitter(); - public request: jest.Mock = jest.fn(); - ping() {} - mockRequest(fn: (stream: FakeStream) => void): FakeStream { - const stream = new FakeStream(); - this.request.mockImplementationOnce(() => { - process.nextTick(fn, stream); - return stream; - }); - return stream; - } - mockUnaryRequest(responseData: Buffer) { - this.mockRequest(stream => { - stream.emit( - 'response', - { ':status': 200, 'content-type': 'application/grpc', 'content-disposition': 'attachment' }, - 4, - ); - stream.emit('data', responseData); - stream.emit('trailers', { 'grpc-status': '0', 'content-disposition': 'attachment' }); - }); - } - close() { - this.emit('close'); - this.socket.emit('close'); - } - ref() {} - unref() {} -} - -function mockHttp2Session(): FakeSession { - const session = new FakeSession(); - spyConnect.mockImplementationOnce(() => { - process.nextTick(() => session.emit('connect')); - return session as unknown as http2.ClientHttp2Session; - }); - return session; -} - -describe('GoogleCloudGrpc tracing', () => { - const mockClient = new NodeClient({ - tracesSampleRate: 1.0, - integrations: [], - dsn: 'https://withAWSServices@domain/123', - transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => Promise.resolve({})), - stackParser: () => [], - }); - - const integration = googleCloudGrpcIntegration(); - mockClient.addIntegration(integration); - - beforeEach(() => { - nock('https://www.googleapis.com').post('/oauth2/v4/token').reply(200, []); - setCurrentClient(mockClient); - mockSpanEnd.mockClear(); - mockStartInactiveSpan.mockClear(); - }); - - afterAll(() => { - nock.restore(); - spyConnect.mockRestore(); - }); - - // We use google cloud pubsub as an example of grpc service for which we can trace requests. - describe('pubsub', () => { - // @ts-expect-error see "Why @ts-expect-error" note - const dnsLookup = dns.lookup as jest.Mock; - // @ts-expect-error see "Why @ts-expect-error" note - const resolveTxt = dns.resolveTxt as jest.Mock; - dnsLookup.mockImplementation((hostname, ...args) => { - expect(hostname).toEqual('pubsub.googleapis.com'); - process.nextTick(args[args.length - 1], null, [{ address: '0.0.0.0', family: 4 }]); - }); - resolveTxt.mockImplementation((hostname, cb) => { - expect(hostname).toEqual('pubsub.googleapis.com'); - process.nextTick(cb, null, []); - }); - - const pubsub = new PubSub({ - credentials: { - client_email: 'client@email', - private_key: fs.readFileSync(path.resolve(__dirname, 'private.pem')).toString(), - }, - projectId: 'project-id', - }); - - afterEach(() => { - dnsLookup.mockReset(); - resolveTxt.mockReset(); - }); - - afterAll(async () => { - await pubsub.close(); - }); - - test('publish', async () => { - mockHttp2Session().mockUnaryRequest(Buffer.from('00000000120a1031363337303834313536363233383630', 'hex')); - const resp = await pubsub.topic('nicetopic').publish(Buffer.from('data')); - expect(resp).toEqual('1637084156623860'); - expect(mockStartInactiveSpan).toBeCalledWith({ - op: 'grpc.pubsub', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.grpc.serverless', - }, - name: 'unary call publish', - onlyIfParent: true, - }); - }); - }); -}); diff --git a/packages/serverless/test/google-cloud-http.test.ts b/packages/serverless/test/google-cloud-http.test.ts deleted file mode 100644 index 804156a1bf02..000000000000 --- a/packages/serverless/test/google-cloud-http.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import { BigQuery } from '@google-cloud/bigquery'; -import * as nock from 'nock'; - -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; -import { NodeClient, createTransport, setCurrentClient } from '@sentry/node-experimental'; -import { googleCloudHttpIntegration } from '../src/google-cloud-http'; - -const mockSpanEnd = jest.fn(); -const mockStartInactiveSpan = jest.fn(spanArgs => ({ ...spanArgs })); - -jest.mock('@sentry/node-experimental', () => { - return { - ...jest.requireActual('@sentry/node-experimental'), - startInactiveSpan: (ctx: unknown) => { - mockStartInactiveSpan(ctx); - return { end: mockSpanEnd }; - }, - }; -}); - -describe('GoogleCloudHttp tracing', () => { - const mockClient = new NodeClient({ - tracesSampleRate: 1.0, - integrations: [], - dsn: 'https://withAWSServices@domain/123', - transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => Promise.resolve({})), - stackParser: () => [], - }); - - const integration = googleCloudHttpIntegration(); - mockClient.addIntegration(integration); - - beforeEach(() => { - nock('https://www.googleapis.com') - .post('/oauth2/v4/token') - .reply(200, '{"access_token":"a.b.c","expires_in":3599,"token_type":"Bearer"}'); - setCurrentClient(mockClient); - mockSpanEnd.mockClear(); - mockStartInactiveSpan.mockClear(); - }); - - afterAll(() => { - nock.restore(); - }); - - // We use google cloud bigquery as an example of http restful service for which we can trace requests. - describe('bigquery', () => { - const bigquery = new BigQuery({ - credentials: { - client_email: 'client@email', - private_key: fs.readFileSync(path.resolve(__dirname, 'private.pem')).toString(), - }, - projectId: 'project-id', - }); - - test('query', async () => { - nock('https://bigquery.googleapis.com') - .post('/bigquery/v2/projects/project-id/jobs') - .query(true) - .reply( - 200, - '{"kind":"bigquery#job","configuration":{"query":{"query":"SELECT true AS foo","destinationTable":{"projectId":"project-id","datasetId":"_7b1eed9bef45ab5fb7345c3d6f662cd767e5ab3e","tableId":"anon101ee25adad33d4f09179679ae9144ad436a210e"},"writeDisposition":"WRITE_TRUNCATE","priority":"INTERACTIVE","useLegacySql":false},"jobType":"QUERY"},"jobReference":{"projectId":"project-id","jobId":"8874c5d5-9cfe-4daa-8390-b0504b97b429","location":"US"},"statistics":{"creationTime":"1603072686488","startTime":"1603072686756","query":{"statementType":"SELECT"}},"status":{"state":"RUNNING"}}', - ); - nock('https://bigquery.googleapis.com') - .get(/^\/bigquery\/v2\/projects\/project-id\/queries\/.+$/) - .query(true) - .reply( - 200, - '{"kind":"bigquery#getQueryResultsResponse","etag":"0+ToZZTzCJ4lyhNI3v4rGg==","schema":{"fields":[{"name":"foo","type":"BOOLEAN","mode":"NULLABLE"}]},"jobReference":{"projectId":"project-id","jobId":"8874c5d5-9cfe-4daa-8390-b0504b97b429","location":"US"},"totalRows":"1","rows":[{"f":[{"v":"true"}]}],"totalBytesProcessed":"0","jobComplete":true,"cacheHit":false}', - ); - const resp = await bigquery.query('SELECT true AS foo'); - expect(resp).toEqual([[{ foo: true }]]); - expect(mockStartInactiveSpan).toBeCalledWith({ - op: 'http.client.bigquery', - name: 'POST /jobs', - onlyIfParent: true, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.serverless', - }, - }); - expect(mockStartInactiveSpan).toBeCalledWith({ - op: 'http.client.bigquery', - name: expect.stringMatching(/^GET \/queries\/.+/), - onlyIfParent: true, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.serverless', - }, - }); - }); - }); -}); diff --git a/packages/serverless/test/private.pem b/packages/serverless/test/private.pem deleted file mode 100644 index 00a658fe7a7f..000000000000 --- a/packages/serverless/test/private.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQDzU+jLTzW6154Joezxrd2+5pCNYP0HcaMoYqEyXfNRpkNE7wrQ -UEG830o4Qcaae2BhqZoujwSW7RkR6h0Fkd0WTR8h5J8rSGNHv/1jJoUUjP9iZ/5S -FAyIIyEYfDPqtnA4iF1QWO2lXWlEFSuZjwM/8jBmeGzoiw17akNThIw8NwIDAQAB -AoGATpboVloEAY/IdFX/QGOmfhTb1T3hG3lheBa695iOkO2BRo9qT7PMN6NqxlbA -PX7ht0lfCfCZS+HSOg4CR50/6WXHMSmwlvcjGuDIDKWjviQTTYE77MlVBQHw9WzY -PfiRBbtouyPGQtO4rk42zkIILC6exBZ1vKpRPOmTAnxrjCECQQD+56r6hYcS6GNp -NOWyv0eVFMBX4iNWAsRf9JVVvGDz2rVuhnkNiN73vfffDWvSXkCydL1jFmalgdQD -gm77UZQHAkEA9F+CauU0aZsJ1SthQ6H0sDQ+eNRUgnz4itnkSC2C20fZ3DaSpCMC -0go81CcZOhftNO730ILqiS67C3d3rqLqUQJBAP10ROHMmz4Fq7MUUcClyPtHIuk/ -hXskTTZL76DMKmrN8NDxDLSUf38+eJRkt+z4osPOp/E6eN3gdXr32nox50kCQCl8 -hXGMU+eR0IuF/88xkY7Qb8KnmWlFuhQohZ7TSyHbAttl0GNZJkNuRYFm2duI8FZK -M3wMnbCIZGy/7WuScOECQQCV+0yrf5dL1M2GHjJfwuTb00wRKalKQEH1v/kvE5vS -FmdN7BPK5Ra50MaecMNoYqu9rmtyWRBn93dcvKrL57nY ------END RSA PRIVATE KEY----- From e658c17670057dd20be1881d9e7169677799abff Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 12 Mar 2024 10:23:55 -0400 Subject: [PATCH 2/7] make CJS only --- MIGRATION.md | 50 +++++++++++++++++++ dev-packages/rollup-utils/npmHelpers.mjs | 15 ++++-- packages/aws-serverless/package.json | 6 --- packages/aws-serverless/rollup.npm.config.mjs | 1 + 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 880c38f99275..30d52a82ac19 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -48,6 +48,7 @@ We've removed the following packages: - [@sentry/hub](./MIGRATION.md#sentryhub) - [@sentry/tracing](./MIGRATION.md#sentrytracing) - [@sentry/integrations](./MIGRATION.md#sentryintegrations) +- [@sentry/serverless](./MIGRATION.md#sentryserverless) #### @sentry/hub @@ -167,6 +168,55 @@ Integrations that are now exported from `@sentry/node` and `@sentry/browser` (or The `Transaction` integration has been removed from `@sentry/integrations`. There is no replacement API. +#### @sentry/serverless + +`@sentry/serverless` has been removed and will no longer be published. The serverless package has been split into two +different packages, `@sentry/aws-serverless` and `@sentry/google-cloud`. These new packages have smaller bundle size +than `@sentry/serverless`, which should improve your serverless cold-start times. + +`@sentry/aws-serverless` and `@sentry/google-cloud` has also been changed to only emit CJS builds. The ESM build for the +`@sentry/serverless` package was always broken and we decided to remove it entirely. ESM support will be re-added at a +later date. + +In `@sentry/serverless` you had to use a namespace import to initialize the SDK. This has been removed so that you can +directly import from the SDK instead. + +```js +// v7 +const Sentry = require('@sentry/serverless'); + +Sentry.AWSLambda.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, +}); + +// v8 +const Sentry = require('@sentry/aws-serverless'); + +Sentry.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, +}); +``` + +```js +// v7 +const Sentry = require('@sentry/serverless'); + +Sentry.GCPFunction.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, +}); + +// v8 +const Sentry = require('@sentry/google-cloud'); + +Sentry.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, +}); +``` + ## 3. Performance Monitoring Changes - [Performance Monitoring API](./MIGRATION.md#performance-monitoring-api) diff --git a/dev-packages/rollup-utils/npmHelpers.mjs b/dev-packages/rollup-utils/npmHelpers.mjs index 76391efebd27..18d8a53a525e 100644 --- a/dev-packages/rollup-utils/npmHelpers.mjs +++ b/dev-packages/rollup-utils/npmHelpers.mjs @@ -119,11 +119,16 @@ export function makeBaseNPMConfig(options = {}) { }); } -export function makeNPMConfigVariants(baseConfig) { - const variantSpecificConfigs = [ - { output: { format: 'cjs', dir: path.join(baseConfig.output.dir, 'cjs') } }, - { output: { format: 'esm', dir: path.join(baseConfig.output.dir, 'esm'), plugins: [makePackageNodeEsm()] } }, - ]; +export function makeNPMConfigVariants(baseConfig, options = {}) { + const { emitEsm = true } = options; + + const variantSpecificConfigs = [{ output: { format: 'cjs', dir: path.join(baseConfig.output.dir, 'cjs') } }]; + + if (emitEsm) { + variantSpecificConfigs.push({ + output: { format: 'esm', dir: path.join(baseConfig.output.dir, 'esm'), plugins: [makePackageNodeEsm()] }, + }); + } return variantSpecificConfigs.map(variant => deepMerge(baseConfig, variant)); } diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index 90a90726fd73..431609842da1 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -11,20 +11,14 @@ }, "files": [ "cjs", - "esm", "types", "types-ts3.8" ], "main": "build/npm/cjs/index.js", - "module": "build/npm/esm/index.js", "types": "build/npm/types/index.d.ts", "exports": { "./package.json": "./package.json", ".": { - "import": { - "types": "./build/npm/types/index.d.ts", - "default": "./build/npm/esm/index.js" - }, "require": { "types": "./build/npm/types/index.d.ts", "default": "./build/npm/cjs/index.js" diff --git a/packages/aws-serverless/rollup.npm.config.mjs b/packages/aws-serverless/rollup.npm.config.mjs index b51a3bdafdb5..ff28359cfeed 100644 --- a/packages/aws-serverless/rollup.npm.config.mjs +++ b/packages/aws-serverless/rollup.npm.config.mjs @@ -9,4 +9,5 @@ export default makeNPMConfigVariants( // packages with bundles have a different build directory structure hasBundles: true, }), + { emitEsm: false }, ); From 9627ca58440874d6388cb90fddf9eac977b172b9 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 12 Mar 2024 11:11:49 -0400 Subject: [PATCH 3/7] correct workspaces --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c2aa2e4879c9..81bc9d16500e 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "packages/angular", "packages/angular-ivy", "packages/astro", + "packages/aws-serverless", "packages/browser", "packages/bun", "packages/core", @@ -68,7 +69,6 @@ "packages/replay", "packages/replay-canvas", "packages/replay-worker", - "packages/serverless", "packages/svelte", "packages/sveltekit", "packages/tracing-internal", From dd8a6d84952548dc76d0c4408911ff8c96c15241 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 12 Mar 2024 12:21:44 -0400 Subject: [PATCH 4/7] use rollup json plugin --- dev-packages/rollup-utils/bundleHelpers.mjs | 5 ++++- dev-packages/rollup-utils/plugins/npmPlugins.mjs | 7 +++++++ package.json | 1 + packages/aws-serverless/rollup.aws.config.mjs | 6 +----- packages/aws-serverless/src/index.awslambda.ts | 8 -------- yarn.lock | 16 ++++++++++++++++ 6 files changed, 29 insertions(+), 14 deletions(-) delete mode 100644 packages/aws-serverless/src/index.awslambda.ts diff --git a/dev-packages/rollup-utils/bundleHelpers.mjs b/dev-packages/rollup-utils/bundleHelpers.mjs index 50476c8f9f05..043a4b2fa501 100644 --- a/dev-packages/rollup-utils/bundleHelpers.mjs +++ b/dev-packages/rollup-utils/bundleHelpers.mjs @@ -11,6 +11,7 @@ import { makeCleanupPlugin, makeCommonJSPlugin, makeIsDebugBuildPlugin, + makeJsonPlugin, makeLicensePlugin, makeNodeResolvePlugin, makeRrwebBuildPlugin, @@ -40,6 +41,8 @@ export function makeBaseBundleConfig(options) { // at all, and without `transformMixedEsModules`, they're only included if they're imported, not if they're required.) const commonJSPlugin = makeCommonJSPlugin({ transformMixedEsModules: true }); + const jsonPlugin = makeJsonPlugin(); + // used by `@sentry/browser` const standAloneBundleConfig = { output: { @@ -89,7 +92,7 @@ export function makeBaseBundleConfig(options) { output: { format: 'cjs', }, - plugins: [commonJSPlugin], + plugins: [jsonPlugin, commonJSPlugin], // Don't bundle any of Node's core modules external: builtinModules, }; diff --git a/dev-packages/rollup-utils/plugins/npmPlugins.mjs b/dev-packages/rollup-utils/plugins/npmPlugins.mjs index 507480c1dd43..7138964a919b 100644 --- a/dev-packages/rollup-utils/plugins/npmPlugins.mjs +++ b/dev-packages/rollup-utils/plugins/npmPlugins.mjs @@ -7,6 +7,7 @@ * Sucrase plugin docs: https://github.com/rollup/plugins/tree/master/packages/sucrase */ +import json from '@rollup/plugin-json'; import replace from '@rollup/plugin-replace'; import sucrase from '@rollup/plugin-sucrase'; import cleanup from 'rollup-plugin-cleanup'; @@ -18,11 +19,17 @@ import cleanup from 'rollup-plugin-cleanup'; */ export function makeSucrasePlugin(options = {}) { return sucrase({ + // Required for bundling OTEL code properly + exclude: ['**/*.json'], transforms: ['typescript', 'jsx'], ...options, }); } +export function makeJsonPlugin() { + return json(); +} + /** * Create a plugin which can be used to pause the build process at the given hook. * diff --git a/package.json b/package.json index 81bc9d16500e..220c6943f868 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "devDependencies": { "@biomejs/biome": "^1.4.0", "@rollup/plugin-commonjs": "^21.0.1", + "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^13.1.3", "@rollup/plugin-replace": "^3.0.1", "@rollup/plugin-sucrase": "^4.0.3", diff --git a/packages/aws-serverless/rollup.aws.config.mjs b/packages/aws-serverless/rollup.aws.config.mjs index dfad060c07fc..22656f397140 100644 --- a/packages/aws-serverless/rollup.aws.config.mjs +++ b/packages/aws-serverless/rollup.aws.config.mjs @@ -6,7 +6,7 @@ export default [ makeBaseBundleConfig({ // this automatically sets it to be CJS bundleType: 'node', - entrypoints: ['src/index.awslambda.ts'], + entrypoints: ['src/index.ts'], licenseTitle: '@sentry/aws-serverless', outputFileBase: () => 'index', packageSpecificConfig: { @@ -21,10 +21,6 @@ export default [ // it to be `index.js` in the build script, since it's standing in for the index file of the npm package. { variants: ['.min.js'] }, ), - - // This builds a wrapper file, which our lambda layer integration automatically sets up to run as soon as node - // launches (via the `NODE_OPTIONS="-r @sentry/aws-serverless/dist/awslambda-auto"` variable). Note the inclusion in this - // path of the legacy `dist` folder; for backwards compatibility, in the build script we'll copy the file there. makeBaseNPMConfig({ entrypoints: ['src/awslambda-auto.ts'], packageSpecificConfig: { diff --git a/packages/aws-serverless/src/index.awslambda.ts b/packages/aws-serverless/src/index.awslambda.ts deleted file mode 100644 index 481c9b13d62f..000000000000 --- a/packages/aws-serverless/src/index.awslambda.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** This file is used as the entrypoint for the lambda layer bundle, and is not included in the npm package. */ - -// https://medium.com/unsplash/named-namespace-imports-7345212bbffb -import * as AWSLambda from './awslambda'; -export { AWSLambda }; - -export * from './awsservices'; -export * from '@sentry/node-experimental'; diff --git a/yarn.lock b/yarn.lock index 447204cfed33..a385e28b9309 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5597,6 +5597,13 @@ dependencies: "@rollup/pluginutils" "^3.0.8" +"@rollup/plugin-json@^6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-6.1.0.tgz#fbe784e29682e9bb6dee28ea75a1a83702e7b805" + integrity sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA== + dependencies: + "@rollup/pluginutils" "^5.1.0" + "@rollup/plugin-node-resolve@^13.0.0": version "13.3.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz#da1c5c5ce8316cef96a2f823d111c1e4e498801c" @@ -5691,6 +5698,15 @@ estree-walker "^2.0.2" picomatch "^2.3.1" +"@rollup/pluginutils@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz#7e53eddc8c7f483a4ad0b94afb1f7f5fd3c771e0" + integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^2.3.1" + "@rollup/rollup-android-arm-eabi@4.9.1": version "4.9.1" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.1.tgz#beaf518ee45a196448e294ad3f823d2d4576cf35" From 2bcf807876ebb8fa4c955a20fb99a5e25552d3c9 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 12 Mar 2024 12:38:35 -0400 Subject: [PATCH 5/7] remove node-experimental --- packages/aws-serverless/src/awsservices.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-serverless/src/awsservices.ts b/packages/aws-serverless/src/awsservices.ts index fa606e89993b..e8a46c6fda00 100644 --- a/packages/aws-serverless/src/awsservices.ts +++ b/packages/aws-serverless/src/awsservices.ts @@ -1,5 +1,5 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration } from '@sentry/core'; -import { getClient, startInactiveSpan } from '@sentry/node-experimental'; +import { getClient, startInactiveSpan } from '@sentry/node'; import type { Client, IntegrationFn, Span } from '@sentry/types'; import { fill } from '@sentry/utils'; // 'aws-sdk/global' import is expected to be type-only so it's erased in the final .js file. From c31b737906857978857cb60e6e342a24d6a5dfc0 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 12 Mar 2024 13:14:31 -0400 Subject: [PATCH 6/7] use require directly --- .../node-exports-test-app/scripts/consistentExports.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts index 5fb4adb20384..959396e26632 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts @@ -1,5 +1,4 @@ import * as SentryAstro from '@sentry/astro'; -import * as SentryAWS from '@sentry/aws-serverless'; import * as SentryBun from '@sentry/bun'; import * as SentryGoogleCloud from '@sentry/google-cloud'; import * as SentryNextJs from '@sentry/nextjs'; @@ -8,6 +7,9 @@ import * as SentryNodeExperimental from '@sentry/node-experimental'; import * as SentryRemix from '@sentry/remix'; import * as SentrySvelteKit from '@sentry/sveltekit'; +// SentryAWS is CJS only +const SentryAWS = require('@sentry/aws-serverless'); + /* List of exports that are safe to ignore / we don't require in any depending package */ const NODE_EXPERIMENTAL_EXPORTS_IGNORE = [ 'default', From da031c47af9c2cf02e992d52321f163c4c74f7c8 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 12 Mar 2024 13:55:52 -0400 Subject: [PATCH 7/7] update exports --- .../node-exports-test-app/scripts/consistentExports.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts index 959396e26632..0f0c4d3ac104 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts @@ -82,7 +82,7 @@ const DEPENDENTS: Dependent[] = [ package: '@sentry/aws-serverless', compareWith: nodeExports, exports: Object.keys(SentryAWS), - ignoreExports: ['cron', 'hapiErrorPlugin'], + ignoreExports: ['makeMain'], }, { package: '@sentry/google-cloud',