From d2c20052dd55cfb6d66b7e924653d79471078805 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 21 Jul 2023 13:32:54 +0200 Subject: [PATCH 01/10] feat(node-experimental): MVP for POTEL --- .craft.yml | 57 +-- docs/new-sdk-release-checklist.md | 2 +- package.json | 1 + packages/node-experimental/.eslintrc.js | 9 + packages/node-experimental/LICENSE | 14 + packages/node-experimental/README.md | 66 +++ packages/node-experimental/jest.config.js | 1 + packages/node-experimental/package.json | 76 +++ .../node-experimental/rollup.npm.config.js | 3 + packages/node-experimental/src/index.ts | 13 + .../src/integrations/express.ts | 27 ++ .../src/integrations/fastify.ts | 27 ++ .../getAutoPerformanceIntegrations.ts | 89 ++++ .../src/integrations/graphql.ts | 31 ++ .../src/integrations/http.ts | 194 ++++++++ .../src/integrations/index.ts | 37 ++ .../src/integrations/lazy.ts | 38 ++ .../src/integrations/mongo.ts | 27 ++ .../src/integrations/mongoose.ts | 27 ++ .../src/integrations/mysql.ts | 27 ++ .../src/integrations/mysql2.ts | 27 ++ .../src/integrations/nest.ts | 27 ++ .../src/integrations/postgres.ts | 27 ++ .../src/integrations/prisma.ts | 31 ++ packages/node-experimental/src/sdk/client.ts | 43 ++ .../node-experimental/src/sdk/handlers.ts | 5 + packages/node-experimental/src/sdk/init.ts | 38 ++ .../node-experimental/src/sdk/initOtel.ts | 38 ++ packages/node-experimental/src/types.ts | 4 + .../src/utils/getRequestSpanData.ts | 39 ++ .../getDefaultNodePreviewClientOptions.ts | 15 + packages/node-experimental/test/sdk.test.ts | 128 +++++ packages/node-experimental/tsconfig.json | 9 + packages/node-experimental/tsconfig.test.json | 12 + .../node-experimental/tsconfig.types.json | 10 + packages/node/src/sdk.ts | 2 +- packages/node/src/types.ts | 9 + packages/opentelemetry-node/package.json | 6 +- packages/opentelemetry-node/src/hooks.ts | 69 +++ packages/opentelemetry-node/src/index.ts | 2 + .../opentelemetry-node/src/spanprocessor.ts | 72 ++- .../opentelemetry-node/src/utils/spanData.ts | 33 ++ yarn.lock | 437 ++++++++++++++++-- 43 files changed, 1751 insertions(+), 98 deletions(-) create mode 100644 packages/node-experimental/.eslintrc.js create mode 100644 packages/node-experimental/LICENSE create mode 100644 packages/node-experimental/README.md create mode 100644 packages/node-experimental/jest.config.js create mode 100644 packages/node-experimental/package.json create mode 100644 packages/node-experimental/rollup.npm.config.js create mode 100644 packages/node-experimental/src/index.ts create mode 100644 packages/node-experimental/src/integrations/express.ts create mode 100644 packages/node-experimental/src/integrations/fastify.ts create mode 100644 packages/node-experimental/src/integrations/getAutoPerformanceIntegrations.ts create mode 100644 packages/node-experimental/src/integrations/graphql.ts create mode 100644 packages/node-experimental/src/integrations/http.ts create mode 100644 packages/node-experimental/src/integrations/index.ts create mode 100644 packages/node-experimental/src/integrations/lazy.ts create mode 100644 packages/node-experimental/src/integrations/mongo.ts create mode 100644 packages/node-experimental/src/integrations/mongoose.ts create mode 100644 packages/node-experimental/src/integrations/mysql.ts create mode 100644 packages/node-experimental/src/integrations/mysql2.ts create mode 100644 packages/node-experimental/src/integrations/nest.ts create mode 100644 packages/node-experimental/src/integrations/postgres.ts create mode 100644 packages/node-experimental/src/integrations/prisma.ts create mode 100644 packages/node-experimental/src/sdk/client.ts create mode 100644 packages/node-experimental/src/sdk/handlers.ts create mode 100644 packages/node-experimental/src/sdk/init.ts create mode 100644 packages/node-experimental/src/sdk/initOtel.ts create mode 100644 packages/node-experimental/src/types.ts create mode 100644 packages/node-experimental/src/utils/getRequestSpanData.ts create mode 100644 packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts create mode 100644 packages/node-experimental/test/sdk.test.ts create mode 100644 packages/node-experimental/tsconfig.json create mode 100644 packages/node-experimental/tsconfig.test.json create mode 100644 packages/node-experimental/tsconfig.types.json create mode 100644 packages/opentelemetry-node/src/hooks.ts create mode 100644 packages/opentelemetry-node/src/utils/spanData.ts diff --git a/.craft.yml b/.craft.yml index c06d90bc7689..0b7faad1f26b 100644 --- a/.craft.yml +++ b/.craft.yml @@ -6,101 +6,104 @@ targets: ## 1. Base Packages, node or browser SDKs depend on ## 1.1 Types - name: npm - id: "@sentry/types" + id: '@sentry/types' includeNames: /^sentry-types-\d.*\.tgz$/ ## 1.2 Utils - name: npm - id: "@sentry/utils" + id: '@sentry/utils' includeNames: /^sentry-utils-\d.*\.tgz$/ ## 1.3 Core SDK - name: npm - id: "@sentry/core" + id: '@sentry/core' includeNames: /^sentry-core-\d.*\.tgz$/ ## 1.4 Tracing package - name: npm - id: "@sentry-internal/tracing" + id: '@sentry-internal/tracing' includeNames: /^sentry-internal-tracing-\d.*\.tgz$/ ## 1.5 Replay package (browser only) - name: npm - id: "@sentry/replay" + id: '@sentry/replay' includeNames: /^sentry-replay-\d.*\.tgz$/ ## 2. Browser & Node SDKs - name: npm - id: "@sentry/browser" + id: '@sentry/browser' includeNames: /^sentry-browser-\d.*\.tgz$/ - name: npm - id: "@sentry/node" + id: '@sentry/node' includeNames: /^sentry-node-\d.*\.tgz$/ ## 3 Browser-based Packages - name: npm - id: "@sentry/angular-ivy" + id: '@sentry/angular-ivy' includeNames: /^sentry-angular-ivy-\d.*\.tgz$/ - name: npm - id: "@sentry/angular" + id: '@sentry/angular' includeNames: /^sentry-angular-\d.*\.tgz$/ - name: npm - id: "@sentry/ember" + id: '@sentry/ember' includeNames: /^sentry-ember-\d.*\.tgz$/ - name: npm - id: "@sentry/react" + id: '@sentry/react' includeNames: /^sentry-react-\d.*\.tgz$/ - name: npm - id: "@sentry/svelte" + id: '@sentry/svelte' includeNames: /^sentry-svelte-\d.*\.tgz$/ - name: npm - id: "@sentry/vue" + id: '@sentry/vue' includeNames: /^sentry-vue-\d.*\.tgz$/ - name: npm - id: "@sentry/wasm" + id: '@sentry/wasm' includeNames: /^sentry-wasm-\d.*\.tgz$/ - name: npm - id: "@sentry/integrations" + id: '@sentry/integrations' includeNames: /^sentry-integrations-\d.*\.tgz$/ ## 4. Node-based Packages - name: npm - id: "@sentry/serverless" + id: '@sentry/serverless' includeNames: /^sentry-serverless-\d.*\.tgz$/ - name: npm - id: "@sentry/opentelemetry-node" + id: '@sentry/opentelemetry-node' includeNames: /^sentry-opentelemetry-node-\d.*\.tgz$/ ## 5. Fullstack/Meta Frameworks (depending on Node and Browser or Framework SDKs) - name: npm - id: "@sentry/nextjs" + id: '@sentry/nextjs' includeNames: /^sentry-nextjs-\d.*\.tgz$/ - name: npm - id: "@sentry/remix" + id: '@sentry/remix' includeNames: /^sentry-remix-\d.*\.tgz$/ - name: npm - id: "@sentry/sveltekit" + id: '@sentry/sveltekit' includeNames: /^sentry-sveltekit-\d.*\.tgz$/ - name: npm - id: "@sentry/gatsby" + id: '@sentry/gatsby' includeNames: /^sentry-gatsby-\d.*\.tgz$/ ## 6. Other Packages ## 6.1 - name: npm - id: "@sentry-internal/typescript" + id: '@sentry-internal/typescript' includeNames: /^sentry-internal-typescript-\d.*\.tgz$/ - name: npm - id: "@sentry-internal/eslint-plugin-sdk" + id: '@sentry-internal/eslint-plugin-sdk' includeNames: /^sentry-internal-eslint-plugin-sdk-\d.*\.tgz$/ ## 6.2 - name: npm - id: "@sentry-internal/eslint-config-sdk" + id: '@sentry-internal/eslint-config-sdk' includeNames: /^sentry-internal-eslint-config-sdk-\d.*\.tgz$/ ## 7. Deprecated packages we still release (but no packages depend on them anymore) - name: npm - id: "@sentry/hub" + id: '@sentry/hub' includeNames: /^sentry-hub-\d.*\.tgz$/ - name: npm - id: "@sentry/tracing" + id: '@sentry/tracing' includeNames: /^sentry-tracing-\d.*\.tgz$/ + - name: npm + id: '@sentry/node-experimental' + includeNames: /^sentry-node-experimental-\d.*\.tgz$/ # AWS Lambda Layer target - name: aws-lambda-layer @@ -118,7 +121,7 @@ targets: # CDN Bundle Target - name: gcs - id: "browser-cdn-bundles" + id: 'browser-cdn-bundles' includeNames: /.*\.js.*$/ bucket: sentry-js-sdk paths: diff --git a/docs/new-sdk-release-checklist.md b/docs/new-sdk-release-checklist.md index aca0f0f399c4..1b40763cdd6c 100644 --- a/docs/new-sdk-release-checklist.md +++ b/docs/new-sdk-release-checklist.md @@ -61,7 +61,7 @@ When you’re ready to make the first release, there are a couple of steps that - [ ] 3) Add an `npm` target in `craft.yml` for the new package. Make sure to insert it in the right place, after all the Sentry dependencies of your package but before packages that depend on your new package (if applicable). ```yml - name: npm - id: npm:@sentry/[yourPackage] + id: '@sentry/[yourPackage]' includeNames: /^sentry-[yourPackage]-\d.*\.tgz$/ ``` - [ ] 4) Cut a new release (as usual, see [Publishing Release](https://github.com/getsentry/sentry-javascript/blob/develop/docs/publishing-a-release.md)) diff --git a/package.json b/package.json index 0d800adc7824..05a48e6d3c4c 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "packages/nextjs", "packages/node", "packages/node-integration-tests", + "packages/node-experimental", "packages/opentelemetry-node", "packages/react", "packages/remix", diff --git a/packages/node-experimental/.eslintrc.js b/packages/node-experimental/.eslintrc.js new file mode 100644 index 000000000000..9899ea1b73d8 --- /dev/null +++ b/packages/node-experimental/.eslintrc.js @@ -0,0 +1,9 @@ +module.exports = { + env: { + node: true, + }, + extends: ['../../.eslintrc.js'], + rules: { + '@sentry-internal/sdk/no-optional-chaining': 'off', + }, +}; diff --git a/packages/node-experimental/LICENSE b/packages/node-experimental/LICENSE new file mode 100644 index 000000000000..4ac873d49f33 --- /dev/null +++ b/packages/node-experimental/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2022 Sentry (https://sentry.io) and individual contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/node-experimental/README.md b/packages/node-experimental/README.md new file mode 100644 index 000000000000..d7d153bff224 --- /dev/null +++ b/packages/node-experimental/README.md @@ -0,0 +1,66 @@ +

+ + Sentry + +

+ +# Official Sentry SDK for Node (EXPERIMENTAL) + +[![npm version](https://img.shields.io/npm/v/@sentry/node-experimental.svg)](https://www.npmjs.com/package/@sentry/node-experimental) +[![npm dm](https://img.shields.io/npm/dm/@sentry/node-experimental.svg)](https://www.npmjs.com/package/@sentry/node-experimental) +[![npm dt](https://img.shields.io/npm/dt/@sentry/node-experimental.svg)](https://www.npmjs.com/package/@sentry/node-experimental) + +This is a WIP, proof of concept implementation of a Node SDK that uses OpenTelemetry for performance instrumentation under the hood. + +THIS MAY/WILL BREAK IN MANY UNEXPECTED WAYS. We may remove, add, change any of the integrations, add/remove any exports, etc. +This package is **NOT READY TO USE IN ANY FORM OF PRODUCTION ENVIRONMENT**! + +This SDK is **considered experimental and in an alpha state**. It may experience breaking changes, and may be discontinued at any time. Please reach out on +[GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have any feedback/concerns. + +## Installation + +```bash +npm install @sentry/node-experimental + +# Or yarn +yarn add @sentry/node-experimental +``` + +## Usage + +```js +// ES5 Syntax +const Sentry = require('@sentry/node-experimental'); +// ES6 Syntax +import * as Sentry from '@sentry/node-experimental'; + +Sentry.init({ + dsn: '__DSN__', + // ... +}); +``` + +Note that it is necessary to initialize Sentry **before you import any package that may be instrumented by us**. + +## Available (Performance) Integrations + +* Http +* Express +* Fastify +* Nest +* Mysql +* Mysql2 +* GraphQL +* Mongo +* Mongoose +* Postgres +* Prisma + +All of these are auto-discovered, you don't need to configure anything for performance. +You still need to register middlewares etc. for error capturing. +Other, non-performance integrations from `@sentry/node` are also available (except for Undici). + +## Links + +- [Official SDK Docs](https://docs.sentry.io/quickstart/) diff --git a/packages/node-experimental/jest.config.js b/packages/node-experimental/jest.config.js new file mode 100644 index 000000000000..24f49ab59a4c --- /dev/null +++ b/packages/node-experimental/jest.config.js @@ -0,0 +1 @@ +module.exports = require('../../jest/jest.config.js'); diff --git a/packages/node-experimental/package.json b/packages/node-experimental/package.json new file mode 100644 index 000000000000..d24cd20c3831 --- /dev/null +++ b/packages/node-experimental/package.json @@ -0,0 +1,76 @@ +{ + "name": "@sentry/node-experimental", + "version": "7.60.0", + "description": "Experimental version of a Node SDK using OpenTelemetry for performance instrumentation", + "repository": "git://github.com/getsentry/sentry-javascript.git", + "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node-experimental", + "author": "Sentry", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "main": "build/cjs/index.js", + "module": "build/esm/index.js", + "types": "build/types/index.d.ts", + "typesVersions": { + "<4.9": { + "build/types/index.d.ts": [ + "build/types-ts3.8/index.d.ts" + ] + } + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@opentelemetry/api": "~1.4.1", + "@opentelemetry/instrumentation": "~0.41.0", + "@opentelemetry/instrumentation-fastify": "~0.32.0", + "@opentelemetry/instrumentation-http": "~0.41.0", + "@opentelemetry/instrumentation-express": "~0.33.0", + "@opentelemetry/instrumentation-graphql": "~0.35.0", + "@opentelemetry/instrumentation-mongodb": "~0.36.0", + "@opentelemetry/instrumentation-mongoose": "~0.33.0", + "@opentelemetry/instrumentation-mysql": "~0.34.0", + "@opentelemetry/instrumentation-mysql2": "~0.34.0", + "@opentelemetry/instrumentation-pg": "~0.36.0", + "@opentelemetry/instrumentation-nestjs-core": "~0.33.0", + "@opentelemetry/semantic-conventions": "~1.15.0", + "@opentelemetry/sdk-trace-node": "~1.15.0", + "@prisma/instrumentation": "~5.0.0", + "@sentry/core": "7.60.0", + "@sentry/node": "7.60.0", + "@sentry/opentelemetry-node": "7.60.0", + "@sentry/types": "7.60.0", + "@sentry/utils": "7.60.0" + }, + "scripts": { + "build": "run-p build:transpile build:types", + "build:dev": "yarn build", + "build:transpile": "rollup -c rollup.npm.config.js", + "build:types": "run-s build:types:core build:types:downlevel", + "build:types:core": "tsc -p tsconfig.types.json", + "build:types:downlevel": "yarn downlevel-dts build/types build/types-ts3.8 --to ts3.8", + "build:watch": "run-p build:transpile:watch build:types:watch", + "build:dev:watch": "yarn build:watch", + "build:transpile:watch": "rollup -c rollup.npm.config.js --watch", + "build:types:watch": "tsc -p tsconfig.types.json --watch", + "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", + "circularDepCheck": "madge --circular src/index.ts", + "clean": "rimraf build coverage sentry-node-experimental-*.tgz", + "fix": "run-s fix:eslint fix:prettier", + "fix:eslint": "eslint . --format stylish --fix", + "fix:prettier": "prettier --write \"{src,test,scripts}/**/**.ts\"", + "lint": "run-s lint:prettier lint:eslint", + "lint:eslint": "eslint . --format stylish", + "lint:prettier": "prettier --check \"{src,test,scripts}/**/**.ts\"", + "test": "yarn test:jest", + "test:jest": "jest", + "test:watch": "jest --watch", + "yalc:publish": "ts-node ../../scripts/prepack.ts && yalc publish build --push" + }, + "volta": { + "extends": "../../package.json" + }, + "sideEffects": false +} diff --git a/packages/node-experimental/rollup.npm.config.js b/packages/node-experimental/rollup.npm.config.js new file mode 100644 index 000000000000..5a62b528ef44 --- /dev/null +++ b/packages/node-experimental/rollup.npm.config.js @@ -0,0 +1,3 @@ +import { makeBaseNPMConfig, makeNPMConfigVariants } from '../../rollup/index.js'; + +export default makeNPMConfigVariants(makeBaseNPMConfig()); diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts new file mode 100644 index 000000000000..1b3e961409e5 --- /dev/null +++ b/packages/node-experimental/src/index.ts @@ -0,0 +1,13 @@ +import { Integrations as CoreIntegrations } from '@sentry/core'; + +import * as NodePreviewIntegrations from './integrations'; + +const INTEGRATIONS = { + ...CoreIntegrations, + ...NodePreviewIntegrations, +}; + +export { init } from './sdk/init'; +export { INTEGRATIONS as Integrations }; +export { getAutoPerformanceIntegrations } from './integrations/getAutoPerformanceIntegrations'; +export * as Handlers from './sdk/handlers'; diff --git a/packages/node-experimental/src/integrations/express.ts b/packages/node-experimental/src/integrations/express.ts new file mode 100644 index 000000000000..f51860364378 --- /dev/null +++ b/packages/node-experimental/src/integrations/express.ts @@ -0,0 +1,27 @@ +import type { Instrumentation } from '@opentelemetry/instrumentation'; +import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express'; +import type { Integration } from '@sentry/types'; + +import { NodePerformanceIntegration } from './lazy'; + +/** + * Express integration + * + * Capture tracing data for express. + */ +export class Express extends NodePerformanceIntegration implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'Express'; + + /** + * @inheritDoc + */ + public name: string = Express.id; + + /** @inheritDoc */ + public setupInstrumentation(): void | Instrumentation[] { + return [new ExpressInstrumentation()]; + } +} diff --git a/packages/node-experimental/src/integrations/fastify.ts b/packages/node-experimental/src/integrations/fastify.ts new file mode 100644 index 000000000000..70969c40be46 --- /dev/null +++ b/packages/node-experimental/src/integrations/fastify.ts @@ -0,0 +1,27 @@ +import type { Instrumentation } from '@opentelemetry/instrumentation'; +import { FastifyInstrumentation } from '@opentelemetry/instrumentation-fastify'; +import type { Integration } from '@sentry/types'; + +import { NodePerformanceIntegration } from './lazy'; + +/** + * Express integration + * + * Capture tracing data for fastify. + */ +export class Fastify extends NodePerformanceIntegration implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'Fastify'; + + /** + * @inheritDoc + */ + public name: string = Fastify.id; + + /** @inheritDoc */ + public setupInstrumentation(): void | Instrumentation[] { + return [new FastifyInstrumentation()]; + } +} diff --git a/packages/node-experimental/src/integrations/getAutoPerformanceIntegrations.ts b/packages/node-experimental/src/integrations/getAutoPerformanceIntegrations.ts new file mode 100644 index 000000000000..d770d50a9a30 --- /dev/null +++ b/packages/node-experimental/src/integrations/getAutoPerformanceIntegrations.ts @@ -0,0 +1,89 @@ +import type { Integration, IntegrationClass } from '@sentry/types'; +import { dynamicRequire } from '@sentry/utils'; + +import type { Express } from './express'; +import type { Fastify } from './fastify'; +import type { GraphQL } from './graphql'; +import type { Mongo } from './mongo'; +import type { Mongoose } from './mongoose'; +import type { Mysql } from './mysql'; +import type { Mysql2 } from './mysql2'; +import type { Nest } from './nest'; +import type { Postgres } from './postgres'; +import type { Prisma } from './prisma'; + +const INTEGRATIONS = [ + () => { + const integration = dynamicRequire(module, './express') as { + Express: IntegrationClass; + }; + return new integration.Express(); + }, + () => { + const integration = dynamicRequire(module, './fastify') as { + Fastify: IntegrationClass; + }; + return new integration.Fastify(); + }, + () => { + const integration = dynamicRequire(module, './graphql') as { + GraphQL: IntegrationClass; + }; + return new integration.GraphQL(); + }, + () => { + const integration = dynamicRequire(module, './mongo') as { + Mongo: IntegrationClass; + }; + return new integration.Mongo(); + }, + () => { + const integration = dynamicRequire(module, './mongoose') as { + Mongoose: IntegrationClass; + }; + return new integration.Mongoose(); + }, + () => { + const integration = dynamicRequire(module, './mysql') as { + Mysql: IntegrationClass; + }; + return new integration.Mysql(); + }, + () => { + const integration = dynamicRequire(module, './mysql2') as { + Mysql2: IntegrationClass; + }; + return new integration.Mysql2(); + }, + () => { + const integration = dynamicRequire(module, './postgres') as { + Postgres: IntegrationClass; + }; + return new integration.Postgres(); + }, + () => { + const integration = dynamicRequire(module, './prisma') as { + Prisma: IntegrationClass; + }; + return new integration.Prisma(); + }, + () => { + const integration = dynamicRequire(module, './nest') as { + Nest: IntegrationClass; + }; + return new integration.Nest(); + }, +]; + +/** TODO */ +export function getAutoPerformanceIntegrations(): Integration[] { + const loadedIntegrations = INTEGRATIONS.map(tryLoad => { + try { + return tryLoad(); + } catch (_) { + return undefined; + } + }).filter(integration => !!integration) as Integration[]; + + return loadedIntegrations; +} diff --git a/packages/node-experimental/src/integrations/graphql.ts b/packages/node-experimental/src/integrations/graphql.ts new file mode 100644 index 000000000000..c606f1b6f126 --- /dev/null +++ b/packages/node-experimental/src/integrations/graphql.ts @@ -0,0 +1,31 @@ +import type { Instrumentation } from '@opentelemetry/instrumentation'; +import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql'; +import type { Integration } from '@sentry/types'; + +import { NodePerformanceIntegration } from './lazy'; + +/** + * GraphQL integration + * + * Capture tracing data for GraphQL. + */ +export class GraphQL extends NodePerformanceIntegration implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'GraphQL'; + + /** + * @inheritDoc + */ + public name: string = GraphQL.id; + + /** @inheritDoc */ + public setupInstrumentation(): void | Instrumentation[] { + return [ + new GraphQLInstrumentation({ + ignoreTrivialResolveSpans: true, + }), + ]; + } +} diff --git a/packages/node-experimental/src/integrations/http.ts b/packages/node-experimental/src/integrations/http.ts new file mode 100644 index 000000000000..dd00f2e3f87a --- /dev/null +++ b/packages/node-experimental/src/integrations/http.ts @@ -0,0 +1,194 @@ +import type { Attributes } from '@opentelemetry/api'; +import { SpanKind } from '@opentelemetry/api'; +import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; +import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-node'; +import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; +import { hasTracingEnabled } from '@sentry/core'; +import { getCurrentHub } from '@sentry/node'; +import type { AdditionalOtelSpanData } from '@sentry/opentelemetry-node'; +import { addOtelSpanData } from '@sentry/opentelemetry-node'; +import type { EventProcessor, Hub, Integration } from '@sentry/types'; +import type { ClientRequest, IncomingMessage, ServerResponse } from 'http'; + +import type { NodePreviewClient } from '../sdk/client'; +import { getRequestSpanData } from '../utils/getRequestSpanData'; + +interface TracingOptions { + /** + * Function determining whether or not to create spans to track outgoing requests to the given URL. + * By default, spans will be created for all outgoing requests. + */ + shouldCreateSpanForRequest?: (url: string) => boolean; +} + +interface HttpOptions { + /** + * Whether breadcrumbs should be recorded for requests + * Defaults to true + */ + breadcrumbs?: boolean; + + /** + * Whether tracing spans should be created for requests + * Defaults to false + */ + tracing?: TracingOptions | boolean; +} + +/** + * Http instrumentation based on @opentelemetry/instrumentation-http. + * This instrumentation does two things: + * * Create breadcrumbs for outgoing requests + * * Create spans for outgoing requests + * + * Note that this integration is also needed for the Express integration to work! + */ +export class Http implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'Http'; + + /** + * @inheritDoc + */ + public name: string = Http.id; + + private _unload?: () => void; + private readonly _breadcrumbs: boolean; + // undefined: default behavior based on tracing settings + private readonly _tracing: boolean | undefined; + private _shouldCreateSpans: boolean = false; + private _shouldCreateSpanForRequest?: (url: string) => boolean; + + /** + * @inheritDoc + */ + public constructor(options: HttpOptions = {}) { + this._breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs; + this._tracing = typeof options.tracing === 'undefined' ? undefined : !!options.tracing; + + if (options.tracing && typeof options.tracing === 'object') { + this._shouldCreateSpanForRequest = options.tracing.shouldCreateSpanForRequest; + } + } + + /** + * @inheritDoc + */ + public setupOnce(_addGlobalEventProcessor: (callback: EventProcessor) => void, _getCurrentHub: () => Hub): void { + // No need to instrument if we don't want to track anything + if (!this._breadcrumbs && this._tracing === false) { + return; + } + + const client = getCurrentHub().getClient(); + const clientOptions = client?.getOptions(); + + this._shouldCreateSpans = typeof this._tracing === 'undefined' ? hasTracingEnabled(clientOptions) : this._tracing; + + // Register instrumentations we care about + this._unload = registerInstrumentations({ + instrumentations: [ + new HttpInstrumentation({ + requireParentforOutgoingSpans: true, + requireParentforIncomingSpans: false, + applyCustomAttributesOnSpan: (span, req, res) => { + this._onSpan(span as unknown as OtelSpan, req, res); + }, + }), + ], + }); + + this._shouldCreateSpanForRequest = + // eslint-disable-next-line deprecation/deprecation + this._shouldCreateSpanForRequest || clientOptions?.shouldCreateSpanForRequest; + + client?.otelHooks.on('spanEnd', this._onSpanEnd); + } + + /** + * Unregister this integration. + */ + public unregister(): void { + this._unload?.(); + + const client = getCurrentHub().getClient(); + client?.otelHooks.off('spanEnd', this._onSpanEnd); + } + + private _onSpanEnd: (otelSpan: OtelSpan) => void | false = (otelSpan: OtelSpan) => { + if (!this._shouldCreateSpans) { + return false; + } + + if (this._shouldCreateSpanForRequest) { + const url = getHttpUrl(otelSpan.attributes); + if (url && !this._shouldCreateSpanForRequest(url)) { + return false; + } + } + + return undefined; + }; + + /** Handle an emitted span from the HTTP instrumentation. */ + private _onSpan( + span: OtelSpan, + request: ClientRequest | IncomingMessage, + response: IncomingMessage | ServerResponse, + ): void { + const data = getRequestSpanData(span, request, response); + const { attributes } = span; + + const additionalData: AdditionalOtelSpanData = { + tags: {}, + data: { + url: data.url, + }, + contexts: {}, + metadata: {}, + }; + + if (span.kind === SpanKind.SERVER) { + additionalData.metadata = { request }; + } + + if (attributes[SemanticAttributes.HTTP_STATUS_CODE]) { + const statusCode = attributes[SemanticAttributes.HTTP_STATUS_CODE] as string; + additionalData.tags['http.status_code'] = statusCode; + additionalData.data['http.response.status_code'] = statusCode; + } + + if (data['http.query']) { + additionalData.data['http.query'] = data['http.query'].slice(1); + } + if (data['http.fragment']) { + additionalData.data['http.fragment'] = data['http.fragment'].slice(1); + } + + addOtelSpanData(span.spanContext().spanId, additionalData); + + getCurrentHub().addBreadcrumb( + { + category: 'http', + data: { + status_code: response.statusCode, + ...data, + }, + type: 'http', + }, + { + event: 'response', + request, + response, + }, + ); + } +} + +function getHttpUrl(attributes: Attributes): string | undefined { + const url = attributes[SemanticAttributes.HTTP_URL]; + return typeof url === 'string' ? url : undefined; +} diff --git a/packages/node-experimental/src/integrations/index.ts b/packages/node-experimental/src/integrations/index.ts new file mode 100644 index 000000000000..efe485d6c1b6 --- /dev/null +++ b/packages/node-experimental/src/integrations/index.ts @@ -0,0 +1,37 @@ +import { Integrations as NodeIntegrations } from '@sentry/node'; + +const { + Console, + OnUncaughtException, + OnUnhandledRejection, + LinkedErrors, + Modules, + ContextLines, + Context, + RequestData, + LocalVariables, +} = NodeIntegrations; + +export { + Console, + OnUncaughtException, + OnUnhandledRejection, + LinkedErrors, + Modules, + ContextLines, + Context, + RequestData, + LocalVariables, +}; + +export { Express } from './express'; +export { Http } from './http'; +export { Fastify } from './fastify'; +export { GraphQL } from './graphql'; +export { Mongo } from './mongo'; +export { Mongoose } from './mongoose'; +export { Mysql } from './mysql'; +export { Mysql2 } from './mysql2'; +export { Nest } from './nest'; +export { Postgres } from './postgres'; +export { Prisma } from './prisma'; diff --git a/packages/node-experimental/src/integrations/lazy.ts b/packages/node-experimental/src/integrations/lazy.ts new file mode 100644 index 000000000000..85545992eb41 --- /dev/null +++ b/packages/node-experimental/src/integrations/lazy.ts @@ -0,0 +1,38 @@ +import type { Instrumentation } from '@opentelemetry/instrumentation'; +import { registerInstrumentations } from '@opentelemetry/instrumentation'; + +/** TODO */ +export abstract class NodePerformanceIntegration { + protected _options: IntegrationOptions; + protected _unload?: () => void; + + public constructor(options: IntegrationOptions) { + this._options = options; + } + + /** + * @inheritDoc + */ + public setupOnce(): void { + const instrumentations = this.setupInstrumentation(this._options); + + if (!instrumentations) { + return; + } + + // Register instrumentations we care about + this._unload = registerInstrumentations({ + instrumentations, + }); + } + + /** + * Unregister this integration. + */ + public unregister(): void { + this._unload?.(); + } + + // Return the instrumentation(s) needed for this integration. + public abstract setupInstrumentation(options: IntegrationOptions): Instrumentation[] | void; +} diff --git a/packages/node-experimental/src/integrations/mongo.ts b/packages/node-experimental/src/integrations/mongo.ts new file mode 100644 index 000000000000..109251986af6 --- /dev/null +++ b/packages/node-experimental/src/integrations/mongo.ts @@ -0,0 +1,27 @@ +import type { Instrumentation } from '@opentelemetry/instrumentation'; +import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb'; +import type { Integration } from '@sentry/types'; + +import { NodePerformanceIntegration } from './lazy'; + +/** + * MongoDB integration + * + * Capture tracing data for MongoDB. + */ +export class Mongo extends NodePerformanceIntegration implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'Mongo'; + + /** + * @inheritDoc + */ + public name: string = Mongo.id; + + /** @inheritDoc */ + public setupInstrumentation(): void | Instrumentation[] { + return [new MongoDBInstrumentation({})]; + } +} diff --git a/packages/node-experimental/src/integrations/mongoose.ts b/packages/node-experimental/src/integrations/mongoose.ts new file mode 100644 index 000000000000..5f85ae2bb771 --- /dev/null +++ b/packages/node-experimental/src/integrations/mongoose.ts @@ -0,0 +1,27 @@ +import type { Instrumentation } from '@opentelemetry/instrumentation'; +import { MongooseInstrumentation } from '@opentelemetry/instrumentation-mongoose'; +import type { Integration } from '@sentry/types'; + +import { NodePerformanceIntegration } from './lazy'; + +/** + * Mongoose integration + * + * Capture tracing data for Mongoose. + */ +export class Mongoose extends NodePerformanceIntegration implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'Mongoose'; + + /** + * @inheritDoc + */ + public name: string = Mongoose.id; + + /** @inheritDoc */ + public setupInstrumentation(): void | Instrumentation[] { + return [new MongooseInstrumentation({})]; + } +} diff --git a/packages/node-experimental/src/integrations/mysql.ts b/packages/node-experimental/src/integrations/mysql.ts new file mode 100644 index 000000000000..bae4e81f21a2 --- /dev/null +++ b/packages/node-experimental/src/integrations/mysql.ts @@ -0,0 +1,27 @@ +import type { Instrumentation } from '@opentelemetry/instrumentation'; +import { MySQLInstrumentation } from '@opentelemetry/instrumentation-mysql'; +import type { Integration } from '@sentry/types'; + +import { NodePerformanceIntegration } from './lazy'; + +/** + * MySQL integration + * + * Capture tracing data for mysql. + */ +export class Mysql extends NodePerformanceIntegration implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'Mysql'; + + /** + * @inheritDoc` + */ + public name: string = Mysql.id; + + /** @inheritDoc */ + public setupInstrumentation(): void | Instrumentation[] { + return [new MySQLInstrumentation({})]; + } +} diff --git a/packages/node-experimental/src/integrations/mysql2.ts b/packages/node-experimental/src/integrations/mysql2.ts new file mode 100644 index 000000000000..0620e7ad0b63 --- /dev/null +++ b/packages/node-experimental/src/integrations/mysql2.ts @@ -0,0 +1,27 @@ +import type { Instrumentation } from '@opentelemetry/instrumentation'; +import { MySQL2Instrumentation } from '@opentelemetry/instrumentation-mysql2'; +import type { Integration } from '@sentry/types'; + +import { NodePerformanceIntegration } from './lazy'; + +/** + * MySQL2 integration + * + * Capture tracing data for mysql2 + */ +export class Mysql2 extends NodePerformanceIntegration implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'Mysql2'; + + /** + * @inheritDoc` + */ + public name: string = Mysql2.id; + + /** @inheritDoc */ + public setupInstrumentation(): void | Instrumentation[] { + return [new MySQL2Instrumentation({})]; + } +} diff --git a/packages/node-experimental/src/integrations/nest.ts b/packages/node-experimental/src/integrations/nest.ts new file mode 100644 index 000000000000..1845e4c3b9c6 --- /dev/null +++ b/packages/node-experimental/src/integrations/nest.ts @@ -0,0 +1,27 @@ +import type { Instrumentation } from '@opentelemetry/instrumentation'; +import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; +import type { Integration } from '@sentry/types'; + +import { NodePerformanceIntegration } from './lazy'; + +/** + * Nest framework integration + * + * Capture tracing data for nest. + */ +export class Nest extends NodePerformanceIntegration implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'Nest'; + + /** + * @inheritDoc` + */ + public name: string = Nest.id; + + /** @inheritDoc */ + public setupInstrumentation(): void | Instrumentation[] { + return [new NestInstrumentation({})]; + } +} diff --git a/packages/node-experimental/src/integrations/postgres.ts b/packages/node-experimental/src/integrations/postgres.ts new file mode 100644 index 000000000000..b061b209c8c8 --- /dev/null +++ b/packages/node-experimental/src/integrations/postgres.ts @@ -0,0 +1,27 @@ +import type { Instrumentation } from '@opentelemetry/instrumentation'; +import { PgInstrumentation } from '@opentelemetry/instrumentation-pg'; +import type { Integration } from '@sentry/types'; + +import { NodePerformanceIntegration } from './lazy'; + +/** + * Postgres integration + * + * Capture tracing data for pg. + */ +export class Postgres extends NodePerformanceIntegration implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'Postgres'; + + /** + * @inheritDoc` + */ + public name: string = Postgres.id; + + /** @inheritDoc */ + public setupInstrumentation(): void | Instrumentation[] { + return [new PgInstrumentation({})]; + } +} diff --git a/packages/node-experimental/src/integrations/prisma.ts b/packages/node-experimental/src/integrations/prisma.ts new file mode 100644 index 000000000000..8fe0b0deb131 --- /dev/null +++ b/packages/node-experimental/src/integrations/prisma.ts @@ -0,0 +1,31 @@ +import type { Instrumentation } from '@opentelemetry/instrumentation'; +import { PrismaInstrumentation } from '@prisma/instrumentation'; +import type { Integration } from '@sentry/types'; + +import { NodePerformanceIntegration } from './lazy'; + +/** + * Prisma integration + * + * Capture tracing data for prisma. + * Note: This requieres to set: + * previewFeatures = ["tracing"] + * For the prisma client. + * See https://www.prisma.io/docs/concepts/components/prisma-client/opentelemetry-tracing for more details. + */ +export class Prisma extends NodePerformanceIntegration implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'Prisma'; + + /** + * @inheritDoc` + */ + public name: string = Prisma.id; + + /** @inheritDoc */ + public setupInstrumentation(): void | Instrumentation[] { + return [new PrismaInstrumentation({})]; + } +} diff --git a/packages/node-experimental/src/sdk/client.ts b/packages/node-experimental/src/sdk/client.ts new file mode 100644 index 000000000000..2f04d1e7d27e --- /dev/null +++ b/packages/node-experimental/src/sdk/client.ts @@ -0,0 +1,43 @@ +import type { Tracer } from '@opentelemetry/api'; +import { trace } from '@opentelemetry/api'; +import { NodeClient, SDK_VERSION } from '@sentry/node'; +import { OtelHooks } from '@sentry/opentelemetry-node'; + +import type { NodePreviewClientOptions } from '../types'; + +/** + * A client built on top of the NodeClient, which provides some otel-specific things on top. + */ +export class NodePreviewClient extends NodeClient { + public otelHooks: OtelHooks; + + private _tracer: Tracer | undefined; + + public constructor(options: ConstructorParameters[0]) { + super(options); + + this.otelHooks = new OtelHooks(); + } + + /** Get the OTEL tracer. */ + public get tracer(): Tracer { + if (this._tracer) { + return this._tracer; + } + + const name = '@sentry/node-experimental'; + const version = SDK_VERSION; + const tracer = trace.getTracer(name, version); + this._tracer = tracer; + + return tracer; + } + + /** + * Get the options for the node preview client. + */ + public getOptions(): NodePreviewClientOptions { + // Just a type-cast, basically + return super.getOptions(); + } +} diff --git a/packages/node-experimental/src/sdk/handlers.ts b/packages/node-experimental/src/sdk/handlers.ts new file mode 100644 index 000000000000..2c9b9b1ad832 --- /dev/null +++ b/packages/node-experimental/src/sdk/handlers.ts @@ -0,0 +1,5 @@ +import { Handlers } from '@sentry/node'; + +const { requestHandler, errorHandler } = Handlers; + +export { requestHandler, errorHandler }; diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node-experimental/src/sdk/init.ts new file mode 100644 index 000000000000..de7556dc71f6 --- /dev/null +++ b/packages/node-experimental/src/sdk/init.ts @@ -0,0 +1,38 @@ +import { hasTracingEnabled } from '@sentry/core'; +import { defaultIntegrations as defaultNodeIntegrations, init as initNode } from '@sentry/node'; + +import { getAutoPerformanceIntegrations } from '../integrations/getAutoPerformanceIntegrations'; +import { Http } from '../integrations/http'; +import type { NodePreviewOptions } from '../types'; +import { NodePreviewClient } from './client'; +import { initOtel } from './initOtel'; + +const ignoredDefaultIntegrations = ['Http', 'Undici']; + +export const defaultIntegrations = [ + ...defaultNodeIntegrations.filter(i => !ignoredDefaultIntegrations.includes(i.name)), + new Http(), +]; + +/** + * Initialize Sentry for Node. + */ +export function init(options: NodePreviewOptions | undefined = {}): void { + const isTracingEnabled = hasTracingEnabled(options); + + options.defaultIntegrations = + options.defaultIntegrations === false + ? [] + : [ + ...(Array.isArray(options.defaultIntegrations) ? options.defaultIntegrations : defaultIntegrations), + ...(isTracingEnabled ? getAutoPerformanceIntegrations() : []), + ]; + + options.instrumenter = 'otel'; + options.clientClass = NodePreviewClient; + + initNode(options); + + // Always init Otel, even if tracing is disabled, because we need it for trace propagation & the HTTP integration + initOtel(); +} diff --git a/packages/node-experimental/src/sdk/initOtel.ts b/packages/node-experimental/src/sdk/initOtel.ts new file mode 100644 index 000000000000..d0e69b4e1883 --- /dev/null +++ b/packages/node-experimental/src/sdk/initOtel.ts @@ -0,0 +1,38 @@ +import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api'; +import { AlwaysOnSampler, NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; +import { getCurrentHub } from '@sentry/core'; +import { SentryPropagator, SentrySpanProcessor } from '@sentry/opentelemetry-node'; + +import type { NodePreviewClient } from './client'; + +/** + * Initialize OpenTelemetry for Node. + * We use the @sentry/opentelemetry-node package to communicate with OpenTelemetry. + */ +export function initOtel(): () => void { + const client = getCurrentHub().getClient(); + + if (client?.getOptions().debug) { + diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); + } + + // We use the custom otelHooks from the client to communicate with @sentry/opentelemetry-node + const hooks = client?.otelHooks; + + // Create and configure NodeTracerProvider + const provider = new NodeTracerProvider({ + sampler: new AlwaysOnSampler(), + }); + provider.addSpanProcessor(new SentrySpanProcessor({ hooks })); + + // Initialize the provider + provider.register({ + propagator: new SentryPropagator(), + }); + + // Cleanup function + return () => { + void provider.forceFlush(); + void provider.shutdown(); + }; +} diff --git a/packages/node-experimental/src/types.ts b/packages/node-experimental/src/types.ts new file mode 100644 index 000000000000..c64d38be8b8d --- /dev/null +++ b/packages/node-experimental/src/types.ts @@ -0,0 +1,4 @@ +import type { NodeClient, NodeOptions } from '@sentry/node'; + +export type NodePreviewOptions = NodeOptions; +export type NodePreviewClientOptions = ConstructorParameters[0]; diff --git a/packages/node-experimental/src/utils/getRequestSpanData.ts b/packages/node-experimental/src/utils/getRequestSpanData.ts new file mode 100644 index 000000000000..8d5d45f5f907 --- /dev/null +++ b/packages/node-experimental/src/utils/getRequestSpanData.ts @@ -0,0 +1,39 @@ +import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; +import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; +import type { SanitizedRequestData } from '@sentry/types'; +import { getSanitizedUrlString, parseUrl } from '@sentry/utils'; +import type { ClientRequest, IncomingMessage, ServerResponse } from 'http'; + +/** + * Get sanitizied request data from an OTEL span. + */ +export function getRequestSpanData( + span: OtelSpan, + _request: ClientRequest | IncomingMessage, + _response: IncomingMessage | ServerResponse, +): SanitizedRequestData { + const data: SanitizedRequestData = { + url: span.attributes[SemanticAttributes.HTTP_URL] as string, + 'http.method': (span.attributes[SemanticAttributes.HTTP_METHOD] as string) || 'GET', + }; + + try { + const urlStr = span.attributes[SemanticAttributes.HTTP_URL]; + if (typeof urlStr === 'string') { + const url = parseUrl(urlStr); + + data.url = getSanitizedUrlString(url); + + if (url.search) { + data['http.query'] = url.search; + } + if (url.hash) { + data['http.fragment'] = url.hash; + } + } + } catch { + // ignore + } + + return data; +} diff --git a/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts b/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts new file mode 100644 index 000000000000..c9f3f9e40d2a --- /dev/null +++ b/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts @@ -0,0 +1,15 @@ +import { createTransport } from '@sentry/core'; +import { resolvedSyncPromise } from '@sentry/utils'; + +import type { NodePreviewClientOptions } from '../../src/types'; + +export function getDefaultNodePreviewClientOptions( + options: Partial = {}, +): NodePreviewClientOptions { + return { + integrations: [], + transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})), + stackParser: () => [], + ...options, + }; +} diff --git a/packages/node-experimental/test/sdk.test.ts b/packages/node-experimental/test/sdk.test.ts new file mode 100644 index 000000000000..ea230368b1d8 --- /dev/null +++ b/packages/node-experimental/test/sdk.test.ts @@ -0,0 +1,128 @@ +import type { Integration } from '@sentry/types'; + +import * as auto from '../src/integrations/getAutoPerformanceIntegrations'; +import { init } from '../src/sdk/init'; +import * as sdk from '../src/sdk/init'; + +// eslint-disable-next-line no-var +declare var global: any; + +const PUBLIC_DSN = 'https://username@domain/123'; + +class MockIntegration implements Integration { + public name: string; + public setupOnce: jest.Mock = jest.fn(); + public constructor(name: string) { + this.name = name; + } +} + +const defaultIntegrationsBackup = sdk.defaultIntegrations; + +describe('init()', () => { + let mockAutoPerformanceIntegrations: jest.SpyInstance = jest.fn(() => []); + + beforeEach(() => { + global.__SENTRY__ = {}; + + mockAutoPerformanceIntegrations = jest.spyOn(auto, 'getAutoPerformanceIntegrations').mockImplementation(() => []); + }); + + afterEach(() => { + // @ts-ignore - Reset the default integrations of node sdk to original + sdk.defaultIntegrations = defaultIntegrationsBackup; + }); + + it("doesn't install default integrations if told not to", () => { + const mockDefaultIntegrations = [ + new MockIntegration('Mock integration 1.1'), + new MockIntegration('Mock integration 1.2'), + ]; + + // @ts-ignore - Replace default integrations with mock integrations, needs ts-ignore because imports are readonly + sdk.defaultIntegrations = mockDefaultIntegrations; + + init({ dsn: PUBLIC_DSN, defaultIntegrations: false }); + + expect(mockDefaultIntegrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(mockDefaultIntegrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(0); + }); + + it('installs merged default integrations, with overrides provided through options', () => { + const mockDefaultIntegrations = [ + new MockIntegration('Some mock integration 2.1'), + new MockIntegration('Some mock integration 2.2'), + ]; + + // @ts-ignore - Replace default integrations with mock integrations, needs ts-ignore because imports are readonly + sdk.defaultIntegrations = mockDefaultIntegrations; + + const mockIntegrations = [ + new MockIntegration('Some mock integration 2.1'), + new MockIntegration('Some mock integration 2.3'), + ]; + + init({ dsn: PUBLIC_DSN, integrations: mockIntegrations }); + + expect(mockDefaultIntegrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(mockDefaultIntegrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockIntegrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockIntegrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(0); + }); + + it('installs integrations returned from a callback function', () => { + const mockDefaultIntegrations = [ + new MockIntegration('Some mock integration 3.1'), + new MockIntegration('Some mock integration 3.2'), + ]; + + // @ts-ignore - Replace default integrations with mock integrations, needs ts-ignore because imports are readonly + sdk.defaultIntegrations = mockDefaultIntegrations; + + const newIntegration = new MockIntegration('Some mock integration 3.3'); + + init({ + dsn: PUBLIC_DSN, + integrations: integrations => { + const newIntegrations = [...integrations]; + newIntegrations[1] = newIntegration; + return newIntegrations; + }, + }); + + expect(mockDefaultIntegrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockDefaultIntegrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(newIntegration.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(0); + }); + + it('installs performance default instrumentations if tracing is enabled', () => { + const mockDefaultIntegrations = [ + new MockIntegration('Some mock integration 4.1'), + new MockIntegration('Some mock integration 4.2'), + ]; + + // @ts-ignore - Replace default integrations with mock integrations, needs ts-ignore because imports are readonly + sdk.defaultIntegrations = mockDefaultIntegrations; + + const autoPerformanceIntegration = new MockIntegration('Some mock integration 4.4'); + + mockAutoPerformanceIntegrations.mockReset().mockImplementation(() => [autoPerformanceIntegration]); + + const mockIntegrations = [ + new MockIntegration('Some mock integration 4.1'), + new MockIntegration('Some mock integration 4.3'), + ]; + + init({ dsn: PUBLIC_DSN, integrations: mockIntegrations, enableTracing: true }); + + expect(mockDefaultIntegrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); + expect(mockDefaultIntegrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockIntegrations[0].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockIntegrations[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(autoPerformanceIntegration.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/node-experimental/tsconfig.json b/packages/node-experimental/tsconfig.json new file mode 100644 index 000000000000..bf45a09f2d71 --- /dev/null +++ b/packages/node-experimental/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + + "include": ["src/**/*"], + + "compilerOptions": { + // package-specific options + } +} diff --git a/packages/node-experimental/tsconfig.test.json b/packages/node-experimental/tsconfig.test.json new file mode 100644 index 000000000000..87f6afa06b86 --- /dev/null +++ b/packages/node-experimental/tsconfig.test.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + + "include": ["test/**/*"], + + "compilerOptions": { + // should include all types from `./tsconfig.json` plus types for all test frameworks used + "types": ["node", "jest"] + + // other package-specific, test-specific options + } +} diff --git a/packages/node-experimental/tsconfig.types.json b/packages/node-experimental/tsconfig.types.json new file mode 100644 index 000000000000..65455f66bd75 --- /dev/null +++ b/packages/node-experimental/tsconfig.types.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "build/types" + } +} diff --git a/packages/node/src/sdk.ts b/packages/node/src/sdk.ts index 06be10d848b6..7287d1938346 100644 --- a/packages/node/src/sdk.ts +++ b/packages/node/src/sdk.ts @@ -168,7 +168,7 @@ export function init(options: NodeOptions = {}): void { transport: options.transport || makeNodeTransport, }; - initAndBind(NodeClient, clientOptions); + initAndBind(options.clientClass || NodeClient, clientOptions); if (options.autoSessionTracking) { startSessionTracking(); diff --git a/packages/node/src/types.ts b/packages/node/src/types.ts index b0ecb354dd82..fb128653870f 100644 --- a/packages/node/src/types.ts +++ b/packages/node/src/types.ts @@ -1,5 +1,6 @@ import type { ClientOptions, Options, SamplingContext } from '@sentry/types'; +import type { NodeClient } from './client'; import type { NodeTransportOptions } from './transports'; export interface BaseNodeOptions { @@ -32,6 +33,14 @@ export interface BaseNodeOptions { */ includeLocalVariables?: boolean; + /** + * Specify a custom NodeClient to be used. Must extend NodeClient! + * This is not a public, supported API, but used internally only. + * + * @hidden + * */ + clientClass?: typeof NodeClient; + // TODO (v8): Remove this in v8 /** * @deprecated Moved to constructor options of the `Http` and `Undici` integration. diff --git a/packages/opentelemetry-node/package.json b/packages/opentelemetry-node/package.json index c965fffc7d56..6efe775179b6 100644 --- a/packages/opentelemetry-node/package.json +++ b/packages/opentelemetry-node/package.json @@ -34,10 +34,10 @@ "@opentelemetry/semantic-conventions": "1.x" }, "devDependencies": { - "@opentelemetry/api": "^1.2.0", + "@opentelemetry/api": "^1.4.1", "@opentelemetry/core": "^1.7.0", - "@opentelemetry/sdk-trace-base": "^1.7.0", - "@opentelemetry/sdk-trace-node": "^1.7.0", + "@opentelemetry/sdk-trace-base": "^1.15.0", + "@opentelemetry/sdk-trace-node": "^1.15.0", "@opentelemetry/semantic-conventions": "^1.7.0", "@sentry/node": "7.60.1" }, diff --git a/packages/opentelemetry-node/src/hooks.ts b/packages/opentelemetry-node/src/hooks.ts new file mode 100644 index 000000000000..db7511a2a43f --- /dev/null +++ b/packages/opentelemetry-node/src/hooks.ts @@ -0,0 +1,69 @@ +import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; +import type { Span } from '@sentry/types'; + +/** + * Keep some otel hooks separate from the default client hooks. + */ +export class OtelHooks { + // eslint-disable-next-line @typescript-eslint/ban-types + private _hooks: Record = {}; + + /** + * Fires when an Otel Span is ended. + * Receives the Otel Span as argument. + * Return `false` from the callback to skip this span in Sentry. + */ + public on(hook: 'spanEnd', callback: (otelSpan: OtelSpan, sentrySpan: Span) => void | false): void; + + /** + * @inheritdoc + */ + public on(hook: string, callback: unknown): void { + if (!this._hooks[hook]) { + this._hooks[hook] = []; + } + + // @ts-ignore We assue the types are correct + this._hooks[hook].push(callback); + } + + /** + * Unregister the hook. + */ + public off(hook: 'spanEnd', callback: (otelSpan: OtelSpan, sentrySpan: Span) => void | false): void; + + /** + * @inheritdoc + */ + public off(hook: string, callback: unknown): void { + if (!this._hooks[hook]) { + return; + } + + // @ts-ignore We assue the types are correct + const index = this._hooks[hook].indexOf(callback); + if (index > -1) { + this._hooks[hook].splice(index, 1); + } + } + + /** + * Emit the otel span end hook. + * Returns `false` if the span should not be sent to Sentry. + */ + public emit(hook: 'spanEnd', otelSpan: OtelSpan, sentrySpan: Span): void | false; + + /** + * @inheritdoc + */ + public emit(hook: string, ...rest: unknown[]): void | false { + if (this._hooks[hook]) { + for (const callback of this._hooks[hook]) { + const cancel = callback(...rest); + if (cancel === false) { + return false; + } + } + } + } +} diff --git a/packages/opentelemetry-node/src/index.ts b/packages/opentelemetry-node/src/index.ts index 752c2cfaa0aa..1ef86c354008 100644 --- a/packages/opentelemetry-node/src/index.ts +++ b/packages/opentelemetry-node/src/index.ts @@ -1,2 +1,4 @@ export { SentrySpanProcessor } from './spanprocessor'; export { SentryPropagator } from './propagator'; +export { OtelHooks } from './hooks'; +export * from './utils/spanData'; diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index 980084a9adec..dbcda429953b 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -7,21 +7,33 @@ import type { DynamicSamplingContext, Span as SentrySpan, TraceparentData, Trans import { isString, logger } from '@sentry/utils'; import { SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, SENTRY_TRACE_PARENT_CONTEXT_KEY } from './constants'; +import type { OtelHooks } from './hooks'; import { isSentryRequestSpan } from './utils/isSentryRequest'; import { mapOtelStatus } from './utils/mapOtelStatus'; import { parseSpanDescription } from './utils/parseOtelSpanDescription'; +import { clearOtelSpanData, getOtelSpanData } from './utils/spanData'; export const SENTRY_SPAN_PROCESSOR_MAP: Map = new Map< SentrySpan['spanId'], SentrySpan >(); +// make sure to remove references in maps, to ensure this can be GCed +function clearSpan(otelSpanId: string): void { + clearOtelSpanData(otelSpanId); + SENTRY_SPAN_PROCESSOR_MAP.delete(otelSpanId); +} + /** * Converts OpenTelemetry Spans to Sentry Spans and sends them to Sentry via * the Sentry SDK. */ export class SentrySpanProcessor implements OtelSpanProcessor { - public constructor() { + private _hooks?: OtelHooks; + + public constructor({ hooks }: { hooks?: OtelHooks } = {}) { + this._hooks = hooks; + addTracingExtensions(); addGlobalEventProcessor(event => { @@ -98,11 +110,17 @@ export class SentrySpanProcessor implements OtelSpanProcessor { // leading to an infinite loop. // In this case, we do not want to finish the span, in order to avoid sending it to Sentry if (isSentryRequestSpan(otelSpan)) { - // Make sure to remove any references, so this can be GCed - SENTRY_SPAN_PROCESSOR_MAP.delete(otelSpanId); + clearSpan(otelSpanId); return; } + if (this._hooks) { + if (this._hooks.emit('spanEnd', otelSpan, sentrySpan) === false) { + clearSpan(otelSpanId); + return; + } + } + otelSpan.events.forEach(event => { if (event.name !== 'exception') { return; @@ -152,7 +170,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor { sentrySpan.finish(convertOtelTimeToSeconds(otelSpan.endTime)); - SENTRY_SPAN_PROCESSOR_MAP.delete(otelSpanId); + clearSpan(otelSpanId); } /** @@ -209,37 +227,57 @@ function updateSpanWithOtelData(sentrySpan: SentrySpan, otelSpan: OtelSpan): voi const { op, description, data } = parseSpanDescription(otelSpan); + const { data: additionalData, tags } = getOtelSpanData(otelSpan.spanContext().spanId); + sentrySpan.setStatus(mapOtelStatus(otelSpan)); sentrySpan.setData('otel.kind', SpanKind[kind]); - Object.keys(attributes).forEach(prop => { - const value = attributes[prop]; - sentrySpan.setData(prop, value); - }); - - if (data) { - Object.keys(data).forEach(prop => { - const value = data[prop]; - sentrySpan.setData(prop, value); + if (tags) { + Object.keys(tags).forEach(prop => { + sentrySpan.setTag(prop, tags[prop]); }); } + const allData = { ...attributes, ...data, ...additionalData }; + + Object.keys(allData).forEach(prop => { + const value = allData[prop]; + sentrySpan.setData(prop, value); + }); + sentrySpan.op = op; sentrySpan.description = description; } function updateTransactionWithOtelData(transaction: Transaction, otelSpan: OtelSpan): void { const { op, description, source, data } = parseSpanDescription(otelSpan); + const { data: additionalData, tags, contexts, metadata } = getOtelSpanData(otelSpan.spanContext().spanId); transaction.setContext('otel', { attributes: otelSpan.attributes, resource: otelSpan.resource.attributes, }); - if (data) { - Object.keys(data).forEach(prop => { - const value = data[prop]; - transaction.setData(prop, value); + if (tags) { + Object.keys(tags).forEach(prop => { + transaction.setTag(prop, tags[prop]); + }); + } + + if (metadata) { + transaction.setMetadata(metadata); + } + + const allData = { ...data, ...additionalData }; + + Object.keys(allData).forEach(prop => { + const value = allData[prop]; + transaction.setData(prop, value); + }); + + if (contexts) { + Object.keys(contexts).forEach(prop => { + transaction.setContext(prop, contexts[prop]); }); } diff --git a/packages/opentelemetry-node/src/utils/spanData.ts b/packages/opentelemetry-node/src/utils/spanData.ts new file mode 100644 index 000000000000..ed83892301d9 --- /dev/null +++ b/packages/opentelemetry-node/src/utils/spanData.ts @@ -0,0 +1,33 @@ +import type { Context } from '@sentry/types'; + +type SentryTags = Record; +type SentryData = Record; +type Contexts = Record; + +export interface AdditionalOtelSpanData { + data: SentryData; + tags: SentryTags; + contexts: Contexts; + metadata: unknown; +} + +const OTEL_SPAN_DATA_MAP: Map = new Map(); + +/** Add data that should be added to the sentry span resulting from the given otel span ID. */ +export function addOtelSpanData(otelSpanId: string, data: AdditionalOtelSpanData): void { + OTEL_SPAN_DATA_MAP.set(otelSpanId, data); +} + +/** Get additional data for a Sentry span. */ +export function getOtelSpanData(otelSpanId: string): AdditionalOtelSpanData { + if (OTEL_SPAN_DATA_MAP.has(otelSpanId)) { + return OTEL_SPAN_DATA_MAP.get(otelSpanId) as AdditionalOtelSpanData; + } + + return { data: {}, tags: {}, contexts: {}, metadata: {} }; +} + +/** Add data that should be added to the sentry span resulting from the given otel span ID. */ +export function clearOtelSpanData(otelSpanId: string): void { + OTEL_SPAN_DATA_MAP.delete(otelSpanId); +} diff --git a/yarn.lock b/yarn.lock index 6619037a8e38..13b2c9fd7840 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3613,6 +3613,11 @@ dependencies: "@opentelemetry/context-base" "^0.14.0" +"@opentelemetry/api@1.4.1", "@opentelemetry/api@^1.4.1", "@opentelemetry/api@~1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.4.1.tgz#ff22eb2e5d476fbc2450a196e40dd243cc20c28f" + integrity sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA== + "@opentelemetry/api@^0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-0.12.0.tgz#0359c3926e8f16fdcd8c78f196bd1e9fc4e66777" @@ -3620,15 +3625,12 @@ dependencies: "@opentelemetry/context-base" "^0.12.0" -"@opentelemetry/api@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.2.0.tgz#89ef99401cde6208cff98760b67663726ef26686" - integrity sha512-0nBr+VZNKm9tvNDZFstI3Pq1fCTEDK5OZTnVKNvBNAKgd0yIvmwsP4m61rEv7ZP+tOUjWJhROpxK5MsnlF911g== - -"@opentelemetry/context-async-hooks@1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.7.0.tgz#b78d1f4f30b484d92d7926dc9d29ec1ccd489cf5" - integrity sha512-g4bMzyVW5dVBeMkyadaf3NRFpmNrdD4Pp9OJsrP29HwIam/zVMNfIWQpT5IBzjtTSMhl/ED5YQYR+UOSjVq3sQ== +"@opentelemetry/context-async-hooks@1.15.0": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.15.0.tgz#134ecbe9096227f72e3178a15287a420a13746a0" + integrity sha512-sfxQOyAyV3WsKswGX0Yx3P+e7t3EtxpF/PC+6e4+rqs88oUfTaP3214iz4GQuuzV9yCG8DRWTZ96J6E/iD0qeA== + dependencies: + tslib "^2.3.1" "@opentelemetry/context-base@^0.12.0": version "0.12.0" @@ -3640,12 +3642,20 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/context-base/-/context-base-0.14.0.tgz#c67fc20a4d891447ca1a855d7d70fa79a3533001" integrity sha512-sDOAZcYwynHFTbLo6n8kIbLiVF3a3BLkrmehJUyEbT9F+Smbi47kLGS2gG2g0fjBLR/Lr1InPD7kXL7FaTqEkw== -"@opentelemetry/core@1.7.0", "@opentelemetry/core@^1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.7.0.tgz#83bdd1b7a4ceafcdffd6590420657caec5f7b34c" - integrity sha512-AVqAi5uc8DrKJBimCTFUT4iFI+5eXpo4sYmGbQ0CypG0piOTHE2g9c5aSoTGYXu3CzOmJZf7pT6Xh+nwm5d6yQ== +"@opentelemetry/core@1.14.0": + version "1.14.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.14.0.tgz#64e876b29cb736c984d54164cd47433f513eafd3" + integrity sha512-MnMZ+sxsnlzloeuXL2nm5QcNczt/iO82UOeQQDHhV83F2fP3sgntW2evvtoxJki0MBLxEsh5ADD7PR/Hn5uzjw== dependencies: - "@opentelemetry/semantic-conventions" "1.7.0" + "@opentelemetry/semantic-conventions" "1.14.0" + +"@opentelemetry/core@1.15.0", "@opentelemetry/core@^1.1.0", "@opentelemetry/core@^1.8.0": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.15.0.tgz#2ba928df0443732825a72a766c2edae9a7f9863f" + integrity sha512-GGTS6BytfaN8OgbCUOnxg/a9WVsVUj0484zXHZuBzvIXx7V4Tmkb0IHnnhS7Q0cBLNLgjNuvrCpQaP8fIvO4bg== + dependencies: + "@opentelemetry/semantic-conventions" "1.15.0" + tslib "^2.3.1" "@opentelemetry/core@^0.12.0": version "0.12.0" @@ -3656,27 +3666,169 @@ "@opentelemetry/context-base" "^0.12.0" semver "^7.1.3" -"@opentelemetry/propagator-b3@1.7.0": +"@opentelemetry/core@^1.7.0": version "1.7.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-b3/-/propagator-b3-1.7.0.tgz#8c089c2bab733ea7122cb4a5f7ffaaa355127555" - integrity sha512-8kKGS1KwArvkThdhubMZlomuREE9FaBcn9L4JrYHh2jly1FZpqOtFNO2byHymVRjH59d43Pa+eJuFpD0Fp7kSw== + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.7.0.tgz#83bdd1b7a4ceafcdffd6590420657caec5f7b34c" + integrity sha512-AVqAi5uc8DrKJBimCTFUT4iFI+5eXpo4sYmGbQ0CypG0piOTHE2g9c5aSoTGYXu3CzOmJZf7pT6Xh+nwm5d6yQ== + dependencies: + "@opentelemetry/semantic-conventions" "1.7.0" + +"@opentelemetry/instrumentation-express@~0.33.0": + version "0.33.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.33.0.tgz#d972ef43420759761f7dbfe78a6ae159cd925a69" + integrity sha512-Cem3AssubzUoBK5ab89rBt2kY90i/FFyQwMC9wPjBQldkOaT4cR+5ufvWritXRfoPltqEeX2imLavujNH6EzCw== dependencies: - "@opentelemetry/core" "1.7.0" + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@types/express" "4.17.13" + tslib "^2.3.1" -"@opentelemetry/propagator-jaeger@1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.7.0.tgz#1c1439866e05ba81da303ad28286aa25d129bf03" - integrity sha512-V7i/L1bx+R/ve4z6dTdn2jtvFxGThRsXS2wNb/tWZVfV8gqnePQp+HfoLrqB/Yz2iRPUcMWrcjx6vV78umvJFA== +"@opentelemetry/instrumentation-fastify@~0.32.0": + version "0.32.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.32.0.tgz#9f0846282f5f428b3ec9a749c1111cf2f153986d" + integrity sha512-M0zRI5+FfA2UnNk9YSeofhZkM5X46w2lDW/1pVahpIlfyPoEbOdRO2YrfR6Y9t9emR62IKk4tN/IcSgXIK2RRg== dependencies: - "@opentelemetry/core" "1.7.0" + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + tslib "^2.3.1" -"@opentelemetry/resources@1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.7.0.tgz#90ccd3a6a86b4dfba4e833e73944bd64958d78c5" - integrity sha512-u1M0yZotkjyKx8dj+46Sg5thwtOTBmtRieNXqdCRiWUp6SfFiIP0bI+1XK3LhuXqXkBXA1awJZaTqKduNMStRg== +"@opentelemetry/instrumentation-graphql@~0.35.0": + version "0.35.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.35.0.tgz#45d2df2be320c7289aff49cabfbbfa8791670b19" + integrity sha512-hKuOXTzB8UBaxVteKU2nMRGnUPt6bD9SSBBLYaDOGGPF1Gs4XsiuMMRuyXomMIudelM7flPpbuqiP9YoSJuXQQ== dependencies: - "@opentelemetry/core" "1.7.0" - "@opentelemetry/semantic-conventions" "1.7.0" + "@opentelemetry/instrumentation" "^0.41.0" + tslib "^2.3.1" + +"@opentelemetry/instrumentation-http@~0.41.0": + version "0.41.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.41.0.tgz#c92ffcb9581544d76d17540f7df442fad8601035" + integrity sha512-O/YTVH4xE96rxRYoo14vayM9s0MUTtMASMAtYS3yvXMJETgc5aFnTrWezKQ6VJ2Lew5qfm1ZISzFURLSUM0qTw== + dependencies: + "@opentelemetry/core" "1.15.0" + "@opentelemetry/instrumentation" "0.41.0" + "@opentelemetry/semantic-conventions" "1.15.0" + semver "^7.5.1" + tslib "^2.3.1" + +"@opentelemetry/instrumentation-mongodb@~0.36.0": + version "0.36.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.36.0.tgz#2ee2acffb1869d9dc3a89e9b879be987e18c1b11" + integrity sha512-bisDLBO0JqydPh4rkgrbYhnFOd4T2ZAnFAnBFll9TB1jJEHTeeobdBThuwxvur5q5ZfbjevreUcMydG6UBNMaA== + dependencies: + "@opentelemetry/instrumentation" "^0.41.0" + "@opentelemetry/sdk-metrics" "^1.9.1" + "@opentelemetry/semantic-conventions" "^1.0.0" + tslib "^2.3.1" + +"@opentelemetry/instrumentation-mongoose@~0.33.0": + version "0.33.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.33.0.tgz#8063740274120105f559bcf663125d3f7cc00cb4" + integrity sha512-IB4zCJ7vsiILx5hjPH7PtlvKIVN84m3rER8zu7xeaMpfpzD5/khj6et0x1aE+KzPS49nIOZx3qmH67MocSNevg== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + tslib "^2.3.1" + +"@opentelemetry/instrumentation-mysql2@~0.34.0": + version "0.34.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.34.0.tgz#48f560b7169128195d19114451c3a5c907b7833a" + integrity sha512-wEJ9BcZTT/60a69V5GS/XlQx+JyoOKKYYR/kdZ2p/XY/UI+kELPSWiLZAR00YLYi33g0zVZlKG3gfHU1KFn5sQ== + dependencies: + "@opentelemetry/instrumentation" "^0.41.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + tslib "^2.3.1" + +"@opentelemetry/instrumentation-mysql@~0.34.0": + version "0.34.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.34.0.tgz#437294f3c8b02ad76b3d56e77f28d9af6cf19c91" + integrity sha512-muBzhaSk3to1XK2aSMFTP9lW6YALebQ8ick9raDBESiLf8JREZDJWVlhfaigeJGBk53tUBZ3Ty1g1Cfe15+OhQ== + dependencies: + "@opentelemetry/instrumentation" "^0.41.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@types/mysql" "2.15.19" + tslib "^2.3.1" + +"@opentelemetry/instrumentation-nestjs-core@~0.33.0": + version "0.33.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.33.0.tgz#f2d64f033f9a84c2ba4ce85c0ec1381ec542b452" + integrity sha512-/c1nipi2XLt2TQlFpKNof44o99H7BmdOWiZBAdIATJpvOKPbn+Ggt4OQQdtmxFyzMX13dTxgtpQ5RX2Orvxz2Q== + dependencies: + "@opentelemetry/instrumentation" "^0.41.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + tslib "^2.3.1" + +"@opentelemetry/instrumentation-pg@~0.36.0": + version "0.36.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.36.0.tgz#43270d2849a0e074ec6eb705dc12216e5c7cc84c" + integrity sha512-S9RmzTILWTl7AVfdp/e8lWQTlpwrpoPbpxAfEJ1ENzTlPMmdw0jWPAQTgrTLQa6cpzhiypDHts3g2b6hc1zFYQ== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.41.0" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@opentelemetry/sql-common" "^0.40.0" + "@types/pg" "8.6.1" + "@types/pg-pool" "2.0.3" + tslib "^2.3.1" + +"@opentelemetry/instrumentation@0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.40.0.tgz#13d5f2d60c3fafef124ab6961a32204f7ef8bb25" + integrity sha512-23TzBKPflUS1uEq5SXymnQKQDSda35KvHjnvxdcDQGE+wg6hwDHgScUCWiBmZW4sxAaPcANfs+Wc9B7yDuyT6Q== + dependencies: + "@types/shimmer" "^1.0.2" + import-in-the-middle "1.3.5" + require-in-the-middle "^7.1.0" + semver "^7.3.2" + shimmer "^1.2.1" + +"@opentelemetry/instrumentation@0.41.0", "@opentelemetry/instrumentation@^0.41.0", "@opentelemetry/instrumentation@~0.41.0": + version "0.41.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.41.0.tgz#a67a7f6d215d8802c2e956907a913b793037e04d" + integrity sha512-Ut9SnZfi7MexOk+GHCMjEtYHogIb6v1dfbnq+oTbQj0lOQUSNLtlO6bXwUdtmPhbvrx6bC0AGr1L6g3rNimv9w== + dependencies: + "@types/shimmer" "^1.0.2" + import-in-the-middle "1.4.1" + require-in-the-middle "^7.1.1" + semver "^7.5.1" + shimmer "^1.2.1" + tslib "^2.3.1" + +"@opentelemetry/propagator-b3@1.15.0": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-b3/-/propagator-b3-1.15.0.tgz#502c3b7030646e7654ce243ae24b67aeac405a53" + integrity sha512-YafSIITpCmo76VdlJ/GvS5x+uuRWCU5BqCOV9CITi11Tk4aqTxMR3pXlMoPYQWstUUgacQf4dGcdvdS+1rkDWQ== + dependencies: + "@opentelemetry/core" "1.15.0" + tslib "^2.3.1" + +"@opentelemetry/propagator-jaeger@1.15.0": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.15.0.tgz#098cfebe885f8c0f4ed3073aa9a7c0aa253086c8" + integrity sha512-OU6WNxuqjxNZoRcIBCsmvTBktAPuBUj1bh+DI+oYAvzwP2NXLavSDJWjVMGTJQDgZuR7lFijmx9EfwyAO9x37Q== + dependencies: + "@opentelemetry/core" "1.15.0" + tslib "^2.3.1" + +"@opentelemetry/resources@1.14.0": + version "1.14.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.14.0.tgz#d6b0a4e71c2706d33c8c6ec7a7b8fea6ad27ddea" + integrity sha512-qRfWIgBxxl3z47E036Aey0Lj2ZjlFb27Q7Xnj1y1z/P293RXJZGLtcfn/w8JF7v1Q2hs3SDGxz7Wb9Dko1YUQA== + dependencies: + "@opentelemetry/core" "1.14.0" + "@opentelemetry/semantic-conventions" "1.14.0" + +"@opentelemetry/resources@1.15.0": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.15.0.tgz#748a6ae9017636b8b30f5dee1fff3e166e51f63d" + integrity sha512-Sb8A6ZXHXDlgHv32UNRE3y8McWE3vkb5dsSttYArYa5ZpwjiF5ge0vnnKUUnG7bY0AgF9VBIOORZE8gsrnD2WA== + dependencies: + "@opentelemetry/core" "1.15.0" + "@opentelemetry/semantic-conventions" "1.15.0" + tslib "^2.3.1" "@opentelemetry/resources@^0.12.0": version "0.12.0" @@ -3686,26 +3838,59 @@ "@opentelemetry/api" "^0.12.0" "@opentelemetry/core" "^0.12.0" -"@opentelemetry/sdk-trace-base@1.7.0", "@opentelemetry/sdk-trace-base@^1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.7.0.tgz#b498424e0c6340a9d80de63fd408c5c2130a60a5" - integrity sha512-Iz84C+FVOskmauh9FNnj4+VrA+hG5o+tkMzXuoesvSfunVSioXib0syVFeNXwOm4+M5GdWCuW632LVjqEXStIg== +"@opentelemetry/sdk-metrics@^1.9.1": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.15.0.tgz#e47ad688882fc2daedcbbe3db16a5c110feb23e8" + integrity sha512-fFUnAcPvlXO39nlIduGuaeCuiZyFtSLCn9gW/0djFRO5DFst4m4gcT6+llXvNWuUvtGB49s56NP10B9IZRN0Rw== dependencies: - "@opentelemetry/core" "1.7.0" - "@opentelemetry/resources" "1.7.0" - "@opentelemetry/semantic-conventions" "1.7.0" + "@opentelemetry/core" "1.15.0" + "@opentelemetry/resources" "1.15.0" + lodash.merge "^4.6.2" + tslib "^2.3.1" -"@opentelemetry/sdk-trace-node@^1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.7.0.tgz#83bf458c33db930144cebed72b524034135fce7b" - integrity sha512-DCAAbi0Zbb1pIofQcKzoAVy9/6bz24asFYeLb4fW/8QYAaawDnxumA++5Huw/RcYdJs8q8AIRBykwjYWWCm/5A== - dependencies: - "@opentelemetry/context-async-hooks" "1.7.0" - "@opentelemetry/core" "1.7.0" - "@opentelemetry/propagator-b3" "1.7.0" - "@opentelemetry/propagator-jaeger" "1.7.0" - "@opentelemetry/sdk-trace-base" "1.7.0" - semver "^7.3.5" +"@opentelemetry/sdk-trace-base@1.14.0": + version "1.14.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.14.0.tgz#831af08f002228a11e577ff860eb6059c8b80fb7" + integrity sha512-NzRGt3PS+HPKfQYMb6Iy8YYc5OKA73qDwci/6ujOIvyW9vcqBJSWbjZ8FeLEAmuatUB5WrRhEKu9b0sIiIYTrQ== + dependencies: + "@opentelemetry/core" "1.14.0" + "@opentelemetry/resources" "1.14.0" + "@opentelemetry/semantic-conventions" "1.14.0" + +"@opentelemetry/sdk-trace-base@1.15.0", "@opentelemetry/sdk-trace-base@^1.15.0": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.15.0.tgz#92340ded8f9fec1aaa63afb40c6e7e01769c2852" + integrity sha512-udt1c9VHipbZwvCPIQR1VLg25Z4AMR/g0X8KmcInbFruGWQ/lptVPkz3yvWAsGSta5yHNQ3uoPwcyCygGnQ6Lg== + dependencies: + "@opentelemetry/core" "1.15.0" + "@opentelemetry/resources" "1.15.0" + "@opentelemetry/semantic-conventions" "1.15.0" + tslib "^2.3.1" + +"@opentelemetry/sdk-trace-node@^1.15.0", "@opentelemetry/sdk-trace-node@~1.15.0": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.15.0.tgz#2d49eafb070a6a53e8df7e91dab30ec2370ba310" + integrity sha512-TKBx9oThZUVKkoGpXhFT/XUgpjq28TWwc6j3JlsL+cJX77DKBnVC+2H+kdVVJHRzyfqDx4LEJJVCwQO3K+cbXA== + dependencies: + "@opentelemetry/context-async-hooks" "1.15.0" + "@opentelemetry/core" "1.15.0" + "@opentelemetry/propagator-b3" "1.15.0" + "@opentelemetry/propagator-jaeger" "1.15.0" + "@opentelemetry/sdk-trace-base" "1.15.0" + semver "^7.5.1" + tslib "^2.3.1" + +"@opentelemetry/semantic-conventions@1.14.0": + version "1.14.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.14.0.tgz#6a729b7f372ce30f77a3f217c09bc216f863fccb" + integrity sha512-rJfCY8rCWz3cb4KI6pEofnytvMPuj3YLQwoscCCYZ5DkdiPjo15IQ0US7+mjcWy9H3fcZIzf2pbJZ7ck/h4tug== + +"@opentelemetry/semantic-conventions@1.15.0", "@opentelemetry/semantic-conventions@^1.0.0", "@opentelemetry/semantic-conventions@~1.15.0": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.15.0.tgz#e6173daa5fd61f353b02c858001388bf26e9d059" + integrity sha512-f3wwFrFyCpGrFBrFs7lCUJSCSCGyeKG52c+EKeobs3Dd29M75yO6GYkt6PkYPfDawxSlV5p+4yJPPk8tPObzTQ== + dependencies: + tslib "^2.3.1" "@opentelemetry/semantic-conventions@1.7.0", "@opentelemetry/semantic-conventions@^1.7.0": version "1.7.0" @@ -3717,6 +3902,13 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-0.12.0.tgz#7e392aecdbdbd5d737d3995998b120dc17589ab0" integrity sha512-BuCcDW0uLNYYTns0/LwXkJ8lp8aDm7kpS+WunEmPAPRSCe6ciOYRvzn5reqJfX93rf+6A3U2SgrBnCTH+0qoQQ== +"@opentelemetry/sql-common@^0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sql-common/-/sql-common-0.40.0.tgz#8cbed0722354d62997c3b9e1adf0e16257be6b15" + integrity sha512-vSqRJYUPJVjMFQpYkQS3ruexCPSZJ8esne3LazLwtCPaPRvzZ7WG3tX44RouAn7w4wMp8orKguBqtt+ng2UTnw== + dependencies: + "@opentelemetry/core" "^1.1.0" + "@opentelemetry/tracing@^0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@opentelemetry/tracing/-/tracing-0.12.0.tgz#769927721d417bfac85eef50c2af068bedce8873" @@ -3773,6 +3965,15 @@ resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e.tgz#bf5e2373ca68ce7556b967cb4965a7095e93fe53" integrity sha512-e3k2Vd606efd1ZYy2NQKkT4C/pn31nehyLhVug6To/q8JT8FpiMrDy7zmm3KLF0L98NOQQcutaVtAPhzKhzn9w== +"@prisma/instrumentation@~5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-5.0.0.tgz#6112461d0ffa401b9278c26d3fef027b0b42820d" + integrity sha512-GUt9ztQXriBmxPIDBd20eBHOX+LaF3zj4ot6mpMB/2YkceU4MvicdEoaX2VkF3r/xM7TatCF9IR2d35wOIv2GA== + dependencies: + "@opentelemetry/api" "1.4.1" + "@opentelemetry/instrumentation" "0.40.0" + "@opentelemetry/sdk-trace-base" "1.14.0" + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -4808,6 +5009,16 @@ "@types/qs" "*" "@types/range-parser" "*" +"@types/express@4.17.13": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/express@4.17.14", "@types/express@^4.17.14", "@types/express@^4.17.2": version "4.17.14" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" @@ -4982,6 +5193,13 @@ "@types/bson" "*" "@types/node" "*" +"@types/mysql@2.15.19": + version "2.15.19" + resolved "https://registry.yarnpkg.com/@types/mysql/-/mysql-2.15.19.tgz#d158927bb7c1a78f77e56de861a3b15cae0e7aed" + integrity sha512-wSRg2QZv14CWcZXkgdvHbbV2ACufNy5EgI8mBBxnJIptchv7DBy/h53VMa2jDhyo0C9MO4iowE6z9vF8Ja1DkQ== + dependencies: + "@types/node" "*" + "@types/mysql@^2.15.21": version "2.15.21" resolved "https://registry.yarnpkg.com/@types/mysql/-/mysql-2.15.21.tgz#7516cba7f9d077f980100c85fd500c8210bd5e45" @@ -5037,6 +5255,31 @@ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.0.tgz#38590dc2c3cf5717154064e3ee9b6947ee21b299" integrity sha512-oPwPSj4a1wu9rsXTEGIJz91ISU725t0BmSnUhb57sI+M8XEmvUop84lzuiYdq0Y5M6xLY8DBPg0C2xEQKLyvBA== +"@types/pg-pool@2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/pg-pool/-/pg-pool-2.0.3.tgz#3eb8df2933f617f219a53091ad4080c94ba1c959" + integrity sha512-fwK5WtG42Yb5RxAwxm3Cc2dJ39FlgcaNiXKvtTLAwtCn642X7dgel+w1+cLWwpSOFImR3YjsZtbkfjxbHtFAeg== + dependencies: + "@types/pg" "*" + +"@types/pg@*": + version "8.10.2" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.10.2.tgz#7814d1ca02c8071f4d0864c1b17c589b061dba43" + integrity sha512-MKFs9P6nJ+LAeHLU3V0cODEOgyThJ3OAnmOlsZsxux6sfQs3HRXR5bBn7xG5DjckEFhTAxsXi7k7cd0pCMxpJw== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^4.0.1" + +"@types/pg@8.6.1": + version "8.6.1" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.1.tgz#099450b8dc977e8197a44f5229cedef95c8747f9" + integrity sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^2.2.0" + "@types/pg@^8.6.5": version "8.6.5" resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.5.tgz#2dce9cb468a6a5e0f1296a59aea3ac75dd27b702" @@ -5166,6 +5409,11 @@ "@types/mime" "^1" "@types/node" "*" +"@types/shimmer@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.0.2.tgz#93eb2c243c351f3f17d5c580c7467ae5d686b65f" + integrity sha512-dKkr1bTxbEsFlh2ARpKzcaAmsYixqt9UyCdoEZk8rHyE4iQYcDCyvSjDSf7JUWJHlJiTtbIoQjxKh6ViywqDAg== + "@types/sinon@^7.0.11": version "7.5.2" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.5.2.tgz#5e2f1d120f07b9cda07e5dedd4f3bf8888fccdb9" @@ -5963,6 +6211,11 @@ acorn-import-assertions@^1.7.6: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== +acorn-import-assertions@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" + integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== + acorn-jsx@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" @@ -9207,6 +9460,11 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== +cjs-module-lexer@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" + integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== + class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -15328,6 +15586,23 @@ import-from@^2.1.0: dependencies: resolve-from "^3.0.0" +import-in-the-middle@1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.3.5.tgz#78384fbcfc7c08faf2b1f61cb94e7dd25651df9c" + integrity sha512-yzHlBqi1EBFrkieAnSt8eTgO5oLSl+YJ7qaOpUH/PMqQOMZoQ/RmDlwnTLQrwYto+gHYjRG+i/IbsB1eDx32NQ== + dependencies: + module-details-from-path "^1.0.3" + +import-in-the-middle@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.4.1.tgz#31b25123bc35d556986a172bb398a3e6c32af9be" + integrity sha512-hGG0PcCsykVo8MBVH8l0uEWLWW6DXMgJA9jvC0yps6M3uIJ8L/tagTCbyF8Ud5TtqJ8/jmZL1YkyySyeVkVQrA== + dependencies: + acorn "^8.8.2" + acorn-import-assertions "^1.9.0" + cjs-module-lexer "^1.2.2" + module-details-from-path "^1.0.3" + import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" @@ -19030,6 +19305,11 @@ module-definition@^3.3.1: ast-module-types "^2.7.1" node-source-walk "^4.0.0" +module-details-from-path@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" + integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== + module-lookup-amd@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/module-lookup-amd/-/module-lookup-amd-7.0.1.tgz#d67c1a93f2ff8e38b8774b99a638e9a4395774b2" @@ -20297,7 +20577,7 @@ object.values@^1.1.0, object.values@^1.1.1, object.values@^1.1.6: define-properties "^1.1.4" es-abstract "^1.20.4" -obuf@^1.0.0, obuf@^1.1.2: +obuf@^1.0.0, obuf@^1.1.2, obuf@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== @@ -21136,6 +21416,11 @@ pg-int8@1.0.1: resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== +pg-numeric@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pg-numeric/-/pg-numeric-1.0.2.tgz#816d9a44026086ae8ae74839acd6a09b0636aa3a" + integrity sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw== + pg-pool@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.5.1.tgz#f499ce76f9bf5097488b3b83b19861f28e4ed905" @@ -21157,6 +21442,19 @@ pg-types@^2.1.0, pg-types@^2.2.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" +pg-types@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-4.0.1.tgz#31857e89d00a6c66b06a14e907c3deec03889542" + integrity sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g== + dependencies: + pg-int8 "1.0.1" + pg-numeric "1.0.2" + postgres-array "~3.0.1" + postgres-bytea "~3.0.0" + postgres-date "~2.0.1" + postgres-interval "^3.0.0" + postgres-range "^1.1.1" + pg@^8.7.3: version "8.7.3" resolved "https://registry.yarnpkg.com/pg/-/pg-8.7.3.tgz#8a5bdd664ca4fda4db7997ec634c6e5455b27c44" @@ -22340,16 +22638,33 @@ postgres-array@~2.0.0: resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== +postgres-array@~3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.2.tgz#68d6182cb0f7f152a7e60dc6a6889ed74b0a5f98" + integrity sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog== + postgres-bytea@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= +postgres-bytea@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-3.0.0.tgz#9048dc461ac7ba70a6a42d109221619ecd1cb089" + integrity sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw== + dependencies: + obuf "~1.1.2" + postgres-date@~1.0.4: version "1.0.7" resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== +postgres-date@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-2.0.1.tgz#638b62e5c33764c292d37b08f5257ecb09231457" + integrity sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw== + postgres-interval@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" @@ -22357,6 +22672,16 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" +postgres-interval@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-3.0.0.tgz#baf7a8b3ebab19b7f38f07566c7aab0962f0c86a" + integrity sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw== + +postgres-range@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.3.tgz#9ccd7b01ca2789eb3c2e0888b3184225fa859f76" + integrity sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g== + precinct@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/precinct/-/precinct-7.1.0.tgz#a0311e0b59029647eaf57c2d30b8efa9c85d129a" @@ -23535,6 +23860,15 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +require-in-the-middle@^7.1.0, require-in-the-middle@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-7.2.0.tgz#b539de8f00955444dc8aed95e17c69b0a4f10fcf" + integrity sha512-3TLx5TGyAY6AOqLBoXmHkNql0HIf2RGbuMgCDT2WO/uGVAPJs6h7Kl+bN6TIZGd9bWhWPwnDnTHGtW8Iu77sdw== + dependencies: + debug "^4.1.1" + module-details-from-path "^1.0.3" + resolve "^1.22.1" + require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -24300,7 +24634,7 @@ semver@7.5.3: dependencies: lru-cache "^6.0.0" -semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: +semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.1: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -24466,6 +24800,11 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== +shimmer@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" From 274423c6353572b91a0570a14266ffab22172a07 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 21 Jul 2023 14:45:26 +0200 Subject: [PATCH 02/10] fix otel tests --- package.json | 2 +- .../opentelemetry-node/test/spanprocessor.test.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 05a48e6d3c4c..849d79b3628a 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "postpublish": "lerna run --stream --concurrency 1 postpublish", "test": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\" test", "test:unit": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\" test:unit", - "test-ci-browser": "lerna run test --ignore \"@sentry/{node,opentelemetry-node,serverless,nextjs,remix,gatsby,sveltekit}\" --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\"", + "test-ci-browser": "lerna run test --ignore \"@sentry/{node,node-experimental,opentelemetry-node,serverless,nextjs,remix,gatsby,sveltekit}\" --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\"", "test-ci-node": "ts-node ./scripts/node-unit-tests.ts", "test:update-snapshots": "lerna run test:update-snapshots", "yalc:publish": "lerna run yalc:publish" diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts index 38c72ef5d3b8..0e479722efe5 100644 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ b/packages/opentelemetry-node/test/spanprocessor.test.ts @@ -71,8 +71,8 @@ describe('SentrySpanProcessor', () => { } it('creates a transaction', async () => { - const startTimestampMs = 1667381672875; - const endTimestampMs = 1667381672309; + const startTimestampMs = 1667381672309; + const endTimestampMs = 1667381672875; const startTime = otelNumberToHrtime(startTimestampMs); const endTime = otelNumberToHrtime(endTimestampMs); @@ -93,8 +93,8 @@ describe('SentrySpanProcessor', () => { }); it('creates a child span if there is a running transaction', () => { - const startTimestampMs = 1667381672875; - const endTimestampMs = 1667381672309; + const startTimestampMs = 1667381672309; + const endTimestampMs = 1667381672875; const startTime = otelNumberToHrtime(startTimestampMs); const endTime = otelNumberToHrtime(endTimestampMs); @@ -176,7 +176,7 @@ describe('SentrySpanProcessor', () => { 'service.name': 'test-service', 'telemetry.sdk.language': 'nodejs', 'telemetry.sdk.name': 'opentelemetry', - 'telemetry.sdk.version': '1.7.0', + 'telemetry.sdk.version': '1.15.0', }, }, }); @@ -201,7 +201,7 @@ describe('SentrySpanProcessor', () => { 'service.name': 'test-service', 'telemetry.sdk.language': 'nodejs', 'telemetry.sdk.name': 'opentelemetry', - 'telemetry.sdk.version': '1.7.0', + 'telemetry.sdk.version': '1.15.0', }, }, }); From d3e393b9caf16caf767ed7163e68316f689366e5 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 24 Jul 2023 13:18:48 +0200 Subject: [PATCH 03/10] skip tests for old node --- scripts/node-unit-tests.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/node-unit-tests.ts b/scripts/node-unit-tests.ts index c32fa6376d38..27238a164d37 100644 --- a/scripts/node-unit-tests.ts +++ b/scripts/node-unit-tests.ts @@ -23,6 +23,7 @@ const NODE_8_SKIP_TESTS_PACKAGES = [ '@sentry/remix', '@sentry/sveltekit', '@sentry-internal/replay-worker', + '@sentry/node-experimental', ]; // We have to downgrade some of our dependencies in order to run tests in Node 8 and 10. @@ -35,10 +36,15 @@ const NODE_8_LEGACY_DEPENDENCIES = [ 'lerna@3.13.4', ]; -const NODE_10_SKIP_TESTS_PACKAGES = ['@sentry/remix', '@sentry/sveltekit', '@sentry-internal/replay-worker']; +const NODE_10_SKIP_TESTS_PACKAGES = [ + '@sentry/remix', + '@sentry/sveltekit', + '@sentry-internal/replay-worker', + '@sentry/node-experimental', +]; const NODE_10_LEGACY_DEPENDENCIES = ['jsdom@16.x', 'lerna@3.13.4']; -const NODE_12_SKIP_TESTS_PACKAGES = ['@sentry/remix', '@sentry/sveltekit']; +const NODE_12_SKIP_TESTS_PACKAGES = ['@sentry/remix', '@sentry/sveltekit', '@sentry/node-experimental']; const NODE_12_LEGACY_DEPENDENCIES = ['lerna@3.13.4']; const NODE_14_SKIP_TESTS_PACKAGES = ['@sentry/sveltekit']; From 3db7c49b075718cdc8d3ec05866a06591367fcb1 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 24 Jul 2023 14:12:13 +0200 Subject: [PATCH 04/10] Apply suggestions from code review Co-authored-by: Lukas Stracke --- packages/node-experimental/LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node-experimental/LICENSE b/packages/node-experimental/LICENSE index 4ac873d49f33..d11896ba1181 100644 --- a/packages/node-experimental/LICENSE +++ b/packages/node-experimental/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022 Sentry (https://sentry.io) and individual contributors. All rights reserved. +Copyright (c) 2023 Sentry (https://sentry.io) and individual contributors. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the From 7a9188c1f8ac7e61bc4541685be978798dcde9d7 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 24 Jul 2023 14:09:18 +0200 Subject: [PATCH 05/10] fix craft oder --- .craft.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.craft.yml b/.craft.yml index 0b7faad1f26b..94e5b37d02a0 100644 --- a/.craft.yml +++ b/.craft.yml @@ -101,6 +101,8 @@ targets: - name: npm id: '@sentry/tracing' includeNames: /^sentry-tracing-\d.*\.tgz$/ + + ## 8. Experimental packages - name: npm id: '@sentry/node-experimental' includeNames: /^sentry-node-experimental-\d.*\.tgz$/ From 31a2fcdeeafe21681e28f8bc1e9a02d44513cf83 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 24 Jul 2023 14:09:31 +0200 Subject: [PATCH 06/10] rename to NodeExperimental* --- packages/node-experimental/src/index.ts | 4 ++-- packages/node-experimental/src/integrations/http.ts | 6 +++--- packages/node-experimental/src/sdk/client.ts | 6 +++--- packages/node-experimental/src/sdk/init.ts | 8 ++++---- packages/node-experimental/src/sdk/initOtel.ts | 4 ++-- packages/node-experimental/src/types.ts | 4 ++-- .../test/helpers/getDefaultNodePreviewClientOptions.ts | 8 ++++---- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index 1b3e961409e5..74108e528513 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -1,10 +1,10 @@ import { Integrations as CoreIntegrations } from '@sentry/core'; -import * as NodePreviewIntegrations from './integrations'; +import * as NodeExperimentalIntegrations from './integrations'; const INTEGRATIONS = { ...CoreIntegrations, - ...NodePreviewIntegrations, + ...NodeExperimentalIntegrations, }; export { init } from './sdk/init'; diff --git a/packages/node-experimental/src/integrations/http.ts b/packages/node-experimental/src/integrations/http.ts index dd00f2e3f87a..01fa77a40cf3 100644 --- a/packages/node-experimental/src/integrations/http.ts +++ b/packages/node-experimental/src/integrations/http.ts @@ -11,7 +11,7 @@ import { addOtelSpanData } from '@sentry/opentelemetry-node'; import type { EventProcessor, Hub, Integration } from '@sentry/types'; import type { ClientRequest, IncomingMessage, ServerResponse } from 'http'; -import type { NodePreviewClient } from '../sdk/client'; +import type { NodeExperimentalClient } from '../sdk/client'; import { getRequestSpanData } from '../utils/getRequestSpanData'; interface TracingOptions { @@ -83,7 +83,7 @@ export class Http implements Integration { return; } - const client = getCurrentHub().getClient(); + const client = getCurrentHub().getClient(); const clientOptions = client?.getOptions(); this._shouldCreateSpans = typeof this._tracing === 'undefined' ? hasTracingEnabled(clientOptions) : this._tracing; @@ -114,7 +114,7 @@ export class Http implements Integration { public unregister(): void { this._unload?.(); - const client = getCurrentHub().getClient(); + const client = getCurrentHub().getClient(); client?.otelHooks.off('spanEnd', this._onSpanEnd); } diff --git a/packages/node-experimental/src/sdk/client.ts b/packages/node-experimental/src/sdk/client.ts index 2f04d1e7d27e..c64f653c869f 100644 --- a/packages/node-experimental/src/sdk/client.ts +++ b/packages/node-experimental/src/sdk/client.ts @@ -3,12 +3,12 @@ import { trace } from '@opentelemetry/api'; import { NodeClient, SDK_VERSION } from '@sentry/node'; import { OtelHooks } from '@sentry/opentelemetry-node'; -import type { NodePreviewClientOptions } from '../types'; +import type { NodeExperimentalClientOptions } from '../types'; /** * A client built on top of the NodeClient, which provides some otel-specific things on top. */ -export class NodePreviewClient extends NodeClient { +export class NodeExperimentalClient extends NodeClient { public otelHooks: OtelHooks; private _tracer: Tracer | undefined; @@ -36,7 +36,7 @@ export class NodePreviewClient extends NodeClient { /** * Get the options for the node preview client. */ - public getOptions(): NodePreviewClientOptions { + public getOptions(): NodeExperimentalClientOptions { // Just a type-cast, basically return super.getOptions(); } diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node-experimental/src/sdk/init.ts index de7556dc71f6..ef451bb83c3a 100644 --- a/packages/node-experimental/src/sdk/init.ts +++ b/packages/node-experimental/src/sdk/init.ts @@ -3,8 +3,8 @@ import { defaultIntegrations as defaultNodeIntegrations, init as initNode } from import { getAutoPerformanceIntegrations } from '../integrations/getAutoPerformanceIntegrations'; import { Http } from '../integrations/http'; -import type { NodePreviewOptions } from '../types'; -import { NodePreviewClient } from './client'; +import type { NodeExperimentalOptions } from '../types'; +import { NodeExperimentalClient } from './client'; import { initOtel } from './initOtel'; const ignoredDefaultIntegrations = ['Http', 'Undici']; @@ -17,7 +17,7 @@ export const defaultIntegrations = [ /** * Initialize Sentry for Node. */ -export function init(options: NodePreviewOptions | undefined = {}): void { +export function init(options: NodeExperimentalOptions | undefined = {}): void { const isTracingEnabled = hasTracingEnabled(options); options.defaultIntegrations = @@ -29,7 +29,7 @@ export function init(options: NodePreviewOptions | undefined = {}): void { ]; options.instrumenter = 'otel'; - options.clientClass = NodePreviewClient; + options.clientClass = NodeExperimentalClient; initNode(options); diff --git a/packages/node-experimental/src/sdk/initOtel.ts b/packages/node-experimental/src/sdk/initOtel.ts index d0e69b4e1883..1b8d875b655c 100644 --- a/packages/node-experimental/src/sdk/initOtel.ts +++ b/packages/node-experimental/src/sdk/initOtel.ts @@ -3,14 +3,14 @@ import { AlwaysOnSampler, NodeTracerProvider } from '@opentelemetry/sdk-trace-no import { getCurrentHub } from '@sentry/core'; import { SentryPropagator, SentrySpanProcessor } from '@sentry/opentelemetry-node'; -import type { NodePreviewClient } from './client'; +import type { NodeExperimentalClient } from './client'; /** * Initialize OpenTelemetry for Node. * We use the @sentry/opentelemetry-node package to communicate with OpenTelemetry. */ export function initOtel(): () => void { - const client = getCurrentHub().getClient(); + const client = getCurrentHub().getClient(); if (client?.getOptions().debug) { diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); diff --git a/packages/node-experimental/src/types.ts b/packages/node-experimental/src/types.ts index c64d38be8b8d..77b1a3ebee4c 100644 --- a/packages/node-experimental/src/types.ts +++ b/packages/node-experimental/src/types.ts @@ -1,4 +1,4 @@ import type { NodeClient, NodeOptions } from '@sentry/node'; -export type NodePreviewOptions = NodeOptions; -export type NodePreviewClientOptions = ConstructorParameters[0]; +export type NodeExperimentalOptions = NodeOptions; +export type NodeExperimentalClientOptions = ConstructorParameters[0]; diff --git a/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts b/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts index c9f3f9e40d2a..c5f2b8b5cf82 100644 --- a/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts +++ b/packages/node-experimental/test/helpers/getDefaultNodePreviewClientOptions.ts @@ -1,11 +1,11 @@ import { createTransport } from '@sentry/core'; import { resolvedSyncPromise } from '@sentry/utils'; -import type { NodePreviewClientOptions } from '../../src/types'; +import type { NodeExperimentalClientOptions } from '../../src/types'; -export function getDefaultNodePreviewClientOptions( - options: Partial = {}, -): NodePreviewClientOptions { +export function getDefaultNodeExperimentalClientOptions( + options: Partial = {}, +): NodeExperimentalClientOptions { return { integrations: [], transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})), From 6c6f867074cb4741e9c1afe0dc555ec553c5de83 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 24 Jul 2023 14:11:51 +0200 Subject: [PATCH 07/10] add sdk metadata --- packages/node-experimental/src/sdk/client.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/node-experimental/src/sdk/client.ts b/packages/node-experimental/src/sdk/client.ts index c64f653c869f..337c50aa481f 100644 --- a/packages/node-experimental/src/sdk/client.ts +++ b/packages/node-experimental/src/sdk/client.ts @@ -14,6 +14,18 @@ export class NodeExperimentalClient extends NodeClient { private _tracer: Tracer | undefined; public constructor(options: ConstructorParameters[0]) { + options._metadata = options._metadata || {}; + options._metadata.sdk = options._metadata.sdk || { + name: 'sentry.javascript.node-experimental', + packages: [ + { + name: 'npm:@sentry/node-experimental', + version: SDK_VERSION, + }, + ], + version: SDK_VERSION, + }; + super(options); this.otelHooks = new OtelHooks(); From 9291bd32294e5758ccff312cef9ab41ac3a41d74 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 24 Jul 2023 14:14:43 +0200 Subject: [PATCH 08/10] fix verdaccio config --- packages/e2e-tests/verdaccio-config/config.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/e2e-tests/verdaccio-config/config.yaml b/packages/e2e-tests/verdaccio-config/config.yaml index 7c5cd16df28d..ce2ef0bd1ada 100644 --- a/packages/e2e-tests/verdaccio-config/config.yaml +++ b/packages/e2e-tests/verdaccio-config/config.yaml @@ -92,6 +92,12 @@ packages: unpublish: $all # proxy: npmjs # Don't proxy for E2E tests! + '@sentry/node-experimental': + access: $all + publish: $all + unpublish: $all + # proxy: npmjs # Don't proxy for E2E tests! + '@sentry/opentelemetry-node': access: $all publish: $all From dd34b8fc5dd00f5eec9d9796d73bea88a459066e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 24 Jul 2023 18:06:00 +0200 Subject: [PATCH 09/10] check for breadcrumbs setting --- .../src/integrations/http.ts | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/node-experimental/src/integrations/http.ts b/packages/node-experimental/src/integrations/http.ts index 01fa77a40cf3..382ca80d4879 100644 --- a/packages/node-experimental/src/integrations/http.ts +++ b/packages/node-experimental/src/integrations/http.ts @@ -170,21 +170,23 @@ export class Http implements Integration { addOtelSpanData(span.spanContext().spanId, additionalData); - getCurrentHub().addBreadcrumb( - { - category: 'http', - data: { - status_code: response.statusCode, - ...data, + if (this._breadcrumbs) { + getCurrentHub().addBreadcrumb( + { + category: 'http', + data: { + status_code: response.statusCode, + ...data, + }, + type: 'http', }, - type: 'http', - }, - { - event: 'response', - request, - response, - }, - ); + { + event: 'response', + request, + response, + }, + ); + } } } From 4319909f70e2a5783ec70e8798089e01b202d803 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 25 Jul 2023 13:12:30 +0200 Subject: [PATCH 10/10] ref: Use client hooks --- packages/core/src/baseclient.ts | 6 ++ .../src/integrations/http.ts | 20 +++--- packages/node-experimental/src/sdk/client.ts | 5 -- .../node-experimental/src/sdk/initOtel.ts | 5 +- packages/opentelemetry-node/src/hooks.ts | 69 ------------------- packages/opentelemetry-node/src/index.ts | 1 - .../opentelemetry-node/src/spanprocessor.ts | 20 +++--- packages/types/src/client.ts | 15 +++- 8 files changed, 41 insertions(+), 100 deletions(-) delete mode 100644 packages/opentelemetry-node/src/hooks.ts diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 52c4301f94ee..e2afccdb0d97 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -388,6 +388,9 @@ export abstract class BaseClient implements Client { /** @inheritdoc */ public on(hook: 'createDsc', callback: (dsc: DynamicSamplingContext) => void): void; + /** @inheritdoc */ + public on(hook: 'otelSpanEnd', callback: (otelSpan: unknown, mutableOptions: { drop: boolean }) => void): void; + /** @inheritdoc */ public on(hook: string, callback: unknown): void { if (!this._hooks[hook]) { @@ -413,6 +416,9 @@ export abstract class BaseClient implements Client { /** @inheritdoc */ public emit(hook: 'createDsc', dsc: DynamicSamplingContext): void; + /** @inheritdoc */ + public emit(hook: 'otelSpanEnd', otelSpan: unknown, mutableOptions: { drop: boolean }): void; + /** @inheritdoc */ public emit(hook: string, ...rest: unknown[]): void { if (this._hooks[hook]) { diff --git a/packages/node-experimental/src/integrations/http.ts b/packages/node-experimental/src/integrations/http.ts index 382ca80d4879..1a01c782053c 100644 --- a/packages/node-experimental/src/integrations/http.ts +++ b/packages/node-experimental/src/integrations/http.ts @@ -105,7 +105,7 @@ export class Http implements Integration { // eslint-disable-next-line deprecation/deprecation this._shouldCreateSpanForRequest || clientOptions?.shouldCreateSpanForRequest; - client?.otelHooks.on('spanEnd', this._onSpanEnd); + client?.on?.('otelSpanEnd', this._onSpanEnd); } /** @@ -113,24 +113,26 @@ export class Http implements Integration { */ public unregister(): void { this._unload?.(); - - const client = getCurrentHub().getClient(); - client?.otelHooks.off('spanEnd', this._onSpanEnd); } - private _onSpanEnd: (otelSpan: OtelSpan) => void | false = (otelSpan: OtelSpan) => { + private _onSpanEnd: (otelSpan: unknown, mutableOptions: { drop: boolean }) => void = ( + otelSpan: unknown, + mutableOptions: { drop: boolean }, + ) => { if (!this._shouldCreateSpans) { - return false; + mutableOptions.drop = true; + return; } if (this._shouldCreateSpanForRequest) { - const url = getHttpUrl(otelSpan.attributes); + const url = getHttpUrl((otelSpan as OtelSpan).attributes); if (url && !this._shouldCreateSpanForRequest(url)) { - return false; + mutableOptions.drop = true; + return; } } - return undefined; + return; }; /** Handle an emitted span from the HTTP instrumentation. */ diff --git a/packages/node-experimental/src/sdk/client.ts b/packages/node-experimental/src/sdk/client.ts index 337c50aa481f..8d7bddad07f6 100644 --- a/packages/node-experimental/src/sdk/client.ts +++ b/packages/node-experimental/src/sdk/client.ts @@ -1,7 +1,6 @@ import type { Tracer } from '@opentelemetry/api'; import { trace } from '@opentelemetry/api'; import { NodeClient, SDK_VERSION } from '@sentry/node'; -import { OtelHooks } from '@sentry/opentelemetry-node'; import type { NodeExperimentalClientOptions } from '../types'; @@ -9,8 +8,6 @@ import type { NodeExperimentalClientOptions } from '../types'; * A client built on top of the NodeClient, which provides some otel-specific things on top. */ export class NodeExperimentalClient extends NodeClient { - public otelHooks: OtelHooks; - private _tracer: Tracer | undefined; public constructor(options: ConstructorParameters[0]) { @@ -27,8 +24,6 @@ export class NodeExperimentalClient extends NodeClient { }; super(options); - - this.otelHooks = new OtelHooks(); } /** Get the OTEL tracer. */ diff --git a/packages/node-experimental/src/sdk/initOtel.ts b/packages/node-experimental/src/sdk/initOtel.ts index 1b8d875b655c..cd2b27b79c05 100644 --- a/packages/node-experimental/src/sdk/initOtel.ts +++ b/packages/node-experimental/src/sdk/initOtel.ts @@ -16,14 +16,11 @@ export function initOtel(): () => void { diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); } - // We use the custom otelHooks from the client to communicate with @sentry/opentelemetry-node - const hooks = client?.otelHooks; - // Create and configure NodeTracerProvider const provider = new NodeTracerProvider({ sampler: new AlwaysOnSampler(), }); - provider.addSpanProcessor(new SentrySpanProcessor({ hooks })); + provider.addSpanProcessor(new SentrySpanProcessor()); // Initialize the provider provider.register({ diff --git a/packages/opentelemetry-node/src/hooks.ts b/packages/opentelemetry-node/src/hooks.ts deleted file mode 100644 index db7511a2a43f..000000000000 --- a/packages/opentelemetry-node/src/hooks.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; -import type { Span } from '@sentry/types'; - -/** - * Keep some otel hooks separate from the default client hooks. - */ -export class OtelHooks { - // eslint-disable-next-line @typescript-eslint/ban-types - private _hooks: Record = {}; - - /** - * Fires when an Otel Span is ended. - * Receives the Otel Span as argument. - * Return `false` from the callback to skip this span in Sentry. - */ - public on(hook: 'spanEnd', callback: (otelSpan: OtelSpan, sentrySpan: Span) => void | false): void; - - /** - * @inheritdoc - */ - public on(hook: string, callback: unknown): void { - if (!this._hooks[hook]) { - this._hooks[hook] = []; - } - - // @ts-ignore We assue the types are correct - this._hooks[hook].push(callback); - } - - /** - * Unregister the hook. - */ - public off(hook: 'spanEnd', callback: (otelSpan: OtelSpan, sentrySpan: Span) => void | false): void; - - /** - * @inheritdoc - */ - public off(hook: string, callback: unknown): void { - if (!this._hooks[hook]) { - return; - } - - // @ts-ignore We assue the types are correct - const index = this._hooks[hook].indexOf(callback); - if (index > -1) { - this._hooks[hook].splice(index, 1); - } - } - - /** - * Emit the otel span end hook. - * Returns `false` if the span should not be sent to Sentry. - */ - public emit(hook: 'spanEnd', otelSpan: OtelSpan, sentrySpan: Span): void | false; - - /** - * @inheritdoc - */ - public emit(hook: string, ...rest: unknown[]): void | false { - if (this._hooks[hook]) { - for (const callback of this._hooks[hook]) { - const cancel = callback(...rest); - if (cancel === false) { - return false; - } - } - } - } -} diff --git a/packages/opentelemetry-node/src/index.ts b/packages/opentelemetry-node/src/index.ts index 1ef86c354008..4f68820d23a2 100644 --- a/packages/opentelemetry-node/src/index.ts +++ b/packages/opentelemetry-node/src/index.ts @@ -1,4 +1,3 @@ export { SentrySpanProcessor } from './spanprocessor'; export { SentryPropagator } from './propagator'; -export { OtelHooks } from './hooks'; export * from './utils/spanData'; diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index dbcda429953b..b2dc2beebf71 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -7,7 +7,6 @@ import type { DynamicSamplingContext, Span as SentrySpan, TraceparentData, Trans import { isString, logger } from '@sentry/utils'; import { SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, SENTRY_TRACE_PARENT_CONTEXT_KEY } from './constants'; -import type { OtelHooks } from './hooks'; import { isSentryRequestSpan } from './utils/isSentryRequest'; import { mapOtelStatus } from './utils/mapOtelStatus'; import { parseSpanDescription } from './utils/parseOtelSpanDescription'; @@ -29,11 +28,7 @@ function clearSpan(otelSpanId: string): void { * the Sentry SDK. */ export class SentrySpanProcessor implements OtelSpanProcessor { - private _hooks?: OtelHooks; - - public constructor({ hooks }: { hooks?: OtelHooks } = {}) { - this._hooks = hooks; - + public constructor() { addTracingExtensions(); addGlobalEventProcessor(event => { @@ -114,11 +109,14 @@ export class SentrySpanProcessor implements OtelSpanProcessor { return; } - if (this._hooks) { - if (this._hooks.emit('spanEnd', otelSpan, sentrySpan) === false) { - clearSpan(otelSpanId); - return; - } + const client = getCurrentHub().getClient(); + + const mutableOptions = { drop: false }; + client && client.emit && client?.emit('otelSpanEnd', otelSpan, mutableOptions); + + if (mutableOptions.drop) { + clearSpan(otelSpanId); + return; } otelSpan.events.forEach(event => { diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index ac779fc3058e..3b9c5835f8b8 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -190,10 +190,16 @@ export interface Client { on?(hook: 'beforeAddBreadcrumb', callback: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => void): void; /** - * Register a callback whena DSC (Dynamic Sampling Context) is created. + * Register a callback when a DSC (Dynamic Sampling Context) is created. */ on?(hook: 'createDsc', callback: (dsc: DynamicSamplingContext) => void): void; + /** + * Register a callback when an OpenTelemetry span is ended (in @sentry/opentelemetry-node). + * The option argument may be mutated to drop the span. + */ + on?(hook: 'otelSpanEnd', callback: (otelSpan: unknown, mutableOptions: { drop: boolean }) => void): void; + /** * Fire a hook event for transaction start and finish. Expects to be given a transaction as the * second argument. @@ -221,4 +227,11 @@ export interface Client { * Fire a hook for when a DSC (Dynamic Sampling Context) is created. Expects the DSC as second argument. */ emit?(hook: 'createDsc', dsc: DynamicSamplingContext): void; + + /** + * Fire a hook for when an OpenTelemetry span is ended (in @sentry/opentelemetry-node). + * Expects the OTEL span & as second argument, and an option object as third argument. + * The option argument may be mutated to drop the span. + */ + emit?(hook: 'otelSpanEnd', otelSpan: unknown, mutableOptions: { drop: boolean }): void; }