From ef6e6e57c933b49586c175159377feac6b82f785 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Mon, 4 Mar 2024 16:18:50 +0000 Subject: [PATCH] feat: Remove `@sentry/opentelemetry-node` package (#10906) Users can/should instead just use `@sentry/node`, or use `@sentry/opentelemetry` themselves. Closes https://github.com/getsentry/sentry-javascript/issues/9843 --- .craft.yml | 5 - .github/ISSUE_TEMPLATE/bug.yml | 3 +- .github/workflows/issue-package-label.yml | 3 - .../test-applications/generic-ts3.8/index.ts | 2 - .../generic-ts3.8/package.json | 1 - .../e2e-tests/verdaccio-config/config.yaml | 6 - package.json | 3 +- packages/core/src/baseclient.ts | 6 - packages/opentelemetry-node/.eslintrc.js | 9 - packages/opentelemetry-node/LICENSE | 14 - packages/opentelemetry-node/README.md | 75 -- packages/opentelemetry-node/jest.config.js | 1 - packages/opentelemetry-node/package.json | 81 -- .../opentelemetry-node/rollup.npm.config.mjs | 3 - packages/opentelemetry-node/src/constants.ts | 9 - .../opentelemetry-node/src/debug-build.ts | 8 - packages/opentelemetry-node/src/index.ts | 5 - packages/opentelemetry-node/src/propagator.ts | 87 -- .../opentelemetry-node/src/spanprocessor.ts | 234 ---- .../utils/captureExceptionForTimedEvent.ts | 56 - .../src/utils/isSentryRequest.ts | 20 - .../src/utils/mapOtelStatus.ts | 75 -- .../src/utils/parseOtelSpanDescription.ts | 165 --- .../opentelemetry-node/src/utils/spanMap.ts | 93 -- .../test/propagator.test.ts | 263 ----- .../test/spanprocessor.test.ts | 1009 ----------------- .../captureExceptionForTimedEvent.test.ts | 147 --- .../utils/parseOtelSpanDescription.test.ts | 144 --- packages/opentelemetry-node/tsconfig.json | 9 - .../opentelemetry-node/tsconfig.test.json | 12 - .../opentelemetry-node/tsconfig.types.json | 10 - packages/types/src/client.ts | 13 - 32 files changed, 2 insertions(+), 2569 deletions(-) delete mode 100644 packages/opentelemetry-node/.eslintrc.js delete mode 100644 packages/opentelemetry-node/LICENSE delete mode 100644 packages/opentelemetry-node/README.md delete mode 100644 packages/opentelemetry-node/jest.config.js delete mode 100644 packages/opentelemetry-node/package.json delete mode 100644 packages/opentelemetry-node/rollup.npm.config.mjs delete mode 100644 packages/opentelemetry-node/src/constants.ts delete mode 100644 packages/opentelemetry-node/src/debug-build.ts delete mode 100644 packages/opentelemetry-node/src/index.ts delete mode 100644 packages/opentelemetry-node/src/propagator.ts delete mode 100644 packages/opentelemetry-node/src/spanprocessor.ts delete mode 100644 packages/opentelemetry-node/src/utils/captureExceptionForTimedEvent.ts delete mode 100644 packages/opentelemetry-node/src/utils/isSentryRequest.ts delete mode 100644 packages/opentelemetry-node/src/utils/mapOtelStatus.ts delete mode 100644 packages/opentelemetry-node/src/utils/parseOtelSpanDescription.ts delete mode 100644 packages/opentelemetry-node/src/utils/spanMap.ts delete mode 100644 packages/opentelemetry-node/test/propagator.test.ts delete mode 100644 packages/opentelemetry-node/test/spanprocessor.test.ts delete mode 100644 packages/opentelemetry-node/test/utils/captureExceptionForTimedEvent.test.ts delete mode 100644 packages/opentelemetry-node/test/utils/parseOtelSpanDescription.test.ts delete mode 100644 packages/opentelemetry-node/tsconfig.json delete mode 100644 packages/opentelemetry-node/tsconfig.test.json delete mode 100644 packages/opentelemetry-node/tsconfig.types.json diff --git a/.craft.yml b/.craft.yml index 9259fc979356..522b6f125f00 100644 --- a/.craft.yml +++ b/.craft.yml @@ -91,9 +91,6 @@ targets: - name: npm id: '@sentry/serverless' includeNames: /^sentry-serverless-\d.*\.tgz$/ - - name: npm - id: '@sentry/opentelemetry-node' - includeNames: /^sentry-opentelemetry-node-\d.*\.tgz$/ - name: npm id: '@sentry/bun' includeNames: /^sentry-bun-\d.*\.tgz$/ @@ -194,8 +191,6 @@ targets: onlyIfPresent: /^sentry-svelte-\d.*\.tgz$/ 'npm:@sentry/sveltekit': onlyIfPresent: /^sentry-sveltekit-\d.*\.tgz$/ - 'npm:@sentry/opentelemetry-node': - onlyIfPresent: /^sentry-opentelemetry-node-\d.*\.tgz$/ 'npm:@sentry/bun': onlyIfPresent: /^sentry-bun-\d.*\.tgz$/ 'npm:@sentry/vercel-edge': diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 61c24641c292..9c8ca1f159b5 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -31,7 +31,7 @@ body: setup. options: - '@sentry/browser' - - '@sentry/astro' + - '@sentry/astro' - '@sentry/angular' - '@sentry/angular-ivy' - '@sentry/bun' @@ -40,7 +40,6 @@ body: - '@sentry/gatsby' - '@sentry/nextjs' - '@sentry/node' - - '@sentry/opentelemetry-node' - '@sentry/react' - '@sentry/remix' - '@sentry/serverless' diff --git a/.github/workflows/issue-package-label.yml b/.github/workflows/issue-package-label.yml index 1c496c927762..c45f0e8359bc 100644 --- a/.github/workflows/issue-package-label.yml +++ b/.github/workflows/issue-package-label.yml @@ -56,9 +56,6 @@ jobs: "@sentry.node": { "label": "Package: Node" }, - "@sentry.opentelemetry-node": { - "label": "Package: otel-node" - }, "@sentry.react": { "label": "Package: react" }, diff --git a/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts b/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts index 48776e59e268..241d82f715a0 100644 --- a/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts +++ b/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts @@ -5,8 +5,6 @@ import * as _SentryCore from '@sentry/core'; // biome-ignore lint/nursery/noUnusedImports: import * as _SentryNode from '@sentry/node'; // biome-ignore lint/nursery/noUnusedImports: -import * as _SentryOpentelemetry from '@sentry/opentelemetry-node'; -// biome-ignore lint/nursery/noUnusedImports: import * as _SentryReplay from '@sentry/replay'; // biome-ignore lint/nursery/noUnusedImports: import * as _SentryTypes from '@sentry/types'; diff --git a/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json b/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json index 8e409a936199..acaa0a116ac0 100644 --- a/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json +++ b/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json @@ -16,7 +16,6 @@ "@sentry/browser": "latest || *", "@sentry/core": "latest || *", "@sentry/node": "latest || *", - "@sentry/opentelemetry-node": "latest || *", "@sentry/replay": "latest || *", "@sentry/types": "latest || *", "@sentry/utils": "latest || *", diff --git a/dev-packages/e2e-tests/verdaccio-config/config.yaml b/dev-packages/e2e-tests/verdaccio-config/config.yaml index 042f93162934..c99f9def69e4 100644 --- a/dev-packages/e2e-tests/verdaccio-config/config.yaml +++ b/dev-packages/e2e-tests/verdaccio-config/config.yaml @@ -104,12 +104,6 @@ packages: unpublish: $all # proxy: npmjs # Don't proxy for E2E tests! - '@sentry/opentelemetry-node': - access: $all - publish: $all - unpublish: $all - # proxy: npmjs # Don't proxy for E2E tests! - '@sentry/opentelemetry': access: $all publish: $all diff --git a/package.json b/package.json index b44b18e87a7a..0c39a0033abb 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,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/{bun,deno,node,node-experimental,opentelemetry-node,profiling-node,serverless,nextjs,remix,gatsby,sveltekit,vercel-edge}\" --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\"", + "test-ci-browser": "lerna run test --ignore \"@sentry/{bun,deno,node,node-experimental,profiling-node,serverless,nextjs,remix,gatsby,sveltekit,vercel-edge}\" --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-ci-bun": "lerna run test --scope @sentry/bun", "test:update-snapshots": "lerna run test:update-snapshots", @@ -61,7 +61,6 @@ "packages/nextjs", "packages/node", "packages/node-experimental", - "packages/opentelemetry-node", "packages/opentelemetry", "packages/profiling-node", "packages/react", diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 21341bef1e42..0f0e92dacd4d 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -443,9 +443,6 @@ 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: 'beforeSendFeedback', @@ -496,9 +493,6 @@ 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: 'beforeSendFeedback', feedback: FeedbackEvent, options?: { includeReplay: boolean }): void; diff --git a/packages/opentelemetry-node/.eslintrc.js b/packages/opentelemetry-node/.eslintrc.js deleted file mode 100644 index 9899ea1b73d8..000000000000 --- a/packages/opentelemetry-node/.eslintrc.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - env: { - node: true, - }, - extends: ['../../.eslintrc.js'], - rules: { - '@sentry-internal/sdk/no-optional-chaining': 'off', - }, -}; diff --git a/packages/opentelemetry-node/LICENSE b/packages/opentelemetry-node/LICENSE deleted file mode 100644 index 4ac873d49f33..000000000000 --- a/packages/opentelemetry-node/LICENSE +++ /dev/null @@ -1,14 +0,0 @@ -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/opentelemetry-node/README.md b/packages/opentelemetry-node/README.md deleted file mode 100644 index 5d09f0edb182..000000000000 --- a/packages/opentelemetry-node/README.md +++ /dev/null @@ -1,75 +0,0 @@ -

- - Sentry - -

- -# Official Sentry SDK for OpenTelemetry Node - -[![npm version](https://img.shields.io/npm/v/@sentry/opentelemetry-node.svg)](https://www.npmjs.com/package/@sentry/opentelemetry-node) -[![npm dm](https://img.shields.io/npm/dm/@sentry/opentelemetry-node.svg)](https://www.npmjs.com/package/@sentry/opentelemetry-node) -[![npm dt](https://img.shields.io/npm/dt/@sentry/opentelemetry-node.svg)](https://www.npmjs.com/package/@sentry/opentelemetry-node) - -This package allows you to send your NodeJS OpenTelemetry trace data to Sentry via OpenTelemetry SpanProcessors. - -This SDK is **considered experimental and in an alpha state**. It may experience breaking changes. 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 @sentry/opentelemetry-node - -# Or yarn -yarn add @sentry/node @sentry/opentelemetry-node -``` - -Note that `@sentry/opentelemetry-node` depends on the following peer dependencies: - -- `@opentelemetry/api` version `1.0.0` or greater -- `@opentelemetry/sdk-trace-base` version `1.0.0` or greater, or a package that implements that, like - `@opentelemetry/sdk-node`. - -## Usage - -You need to register the `SentrySpanProcessor` and `SentryPropagator` with your OpenTelemetry installation: - -```js -const Sentry = require("@sentry/node"); -const { - SentrySpanProcessor, - SentryPropagator, -} = require("@sentry/opentelemetry-node"); - -const opentelemetry = require("@opentelemetry/sdk-node"); -const otelApi = require("@opentelemetry/api"); -const { - getNodeAutoInstrumentations, -} = require("@opentelemetry/auto-instrumentations-node"); -const { - OTLPTraceExporter, -} = require("@opentelemetry/exporter-trace-otlp-grpc"); - -// Make sure to call `Sentry.init` BEFORE initializing the OpenTelemetry SDK -Sentry.init({ - dsn: '__DSN__', - tracesSampleRate: 1.0, - // ... -}); - -const sdk = new opentelemetry.NodeSDK({ - // Existing config - traceExporter: new OTLPTraceExporter(), - instrumentations: [getNodeAutoInstrumentations()], - - // Sentry config - spanProcessor: new SentrySpanProcessor(), - textMapPropagator: new SentryPropagator(), -}); - -sdk.start(); -``` - -## Links - -- [Official SDK Docs](https://docs.sentry.io/quickstart/) diff --git a/packages/opentelemetry-node/jest.config.js b/packages/opentelemetry-node/jest.config.js deleted file mode 100644 index 24f49ab59a4c..000000000000 --- a/packages/opentelemetry-node/jest.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../../jest/jest.config.js'); diff --git a/packages/opentelemetry-node/package.json b/packages/opentelemetry-node/package.json deleted file mode 100644 index 27f11e28933e..000000000000 --- a/packages/opentelemetry-node/package.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "name": "@sentry/opentelemetry-node", - "version": "8.0.0-alpha.0", - "description": "Official Sentry SDK for OpenTelemetry Node.js", - "repository": "git://github.com/getsentry/sentry-javascript.git", - "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/opentelemetry-node", - "author": "Sentry", - "license": "MIT", - "engines": { - "node": ">=14.8" - }, - "files": [ - "cjs", - "esm", - "types", - "types-ts3.8" - ], - "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": { - "@sentry/core": "8.0.0-alpha.0", - "@sentry/types": "8.0.0-alpha.0", - "@sentry/utils": "8.0.0-alpha.0" - }, - "peerDependencies": { - "@opentelemetry/api": "1.x", - "@opentelemetry/core": "1.x", - "@opentelemetry/sdk-trace-base": "1.x", - "@opentelemetry/semantic-conventions": "1.x" - }, - "devDependencies": { - "@opentelemetry/api": "^1.6.0", - "@opentelemetry/core": "^1.17.1", - "@opentelemetry/sdk-trace-base": "^1.17.1", - "@opentelemetry/sdk-trace-node": "^1.17.1", - "@opentelemetry/semantic-conventions": "^1.17.1", - "@sentry/node-experimental": "8.0.0-alpha.0" - }, - "scripts": { - "build": "run-p build:transpile build:types", - "build:dev": "yarn build", - "build:transpile": "rollup -c rollup.npm.config.mjs", - "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.mjs --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-opentelemetry-node-*.tgz", - "fix": "eslint . --format stylish --fix", - "lint": "eslint . --format stylish", - "test": "yarn test:jest", - "test:jest": "jest", - "test:watch": "jest --watch", - "yalc:publish": "ts-node ../../scripts/prepack.ts && yalc publish build --push --sig" - }, - "volta": { - "extends": "../../package.json" - }, - "sideEffects": [ - "./cjs/index.js", - "./esm/index.js", - "./build/npm/cjs/index.js", - "./build/npm/esm/index.js", - "./src/index.ts" - ] -} diff --git a/packages/opentelemetry-node/rollup.npm.config.mjs b/packages/opentelemetry-node/rollup.npm.config.mjs deleted file mode 100644 index 84a06f2fb64a..000000000000 --- a/packages/opentelemetry-node/rollup.npm.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; - -export default makeNPMConfigVariants(makeBaseNPMConfig()); diff --git a/packages/opentelemetry-node/src/constants.ts b/packages/opentelemetry-node/src/constants.ts deleted file mode 100644 index 55f386f2b39f..000000000000 --- a/packages/opentelemetry-node/src/constants.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createContextKey } from '@opentelemetry/api'; - -export const SENTRY_TRACE_HEADER = 'sentry-trace'; - -export const SENTRY_BAGGAGE_HEADER = 'baggage'; - -export const SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY = createContextKey('SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY'); - -export const SENTRY_TRACE_PARENT_CONTEXT_KEY = createContextKey('SENTRY_TRACE_PARENT_CONTEXT_KEY'); diff --git a/packages/opentelemetry-node/src/debug-build.ts b/packages/opentelemetry-node/src/debug-build.ts deleted file mode 100644 index 60aa50940582..000000000000 --- a/packages/opentelemetry-node/src/debug-build.ts +++ /dev/null @@ -1,8 +0,0 @@ -declare const __DEBUG_BUILD__: boolean; - -/** - * This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code. - * - * ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking. - */ -export const DEBUG_BUILD = __DEBUG_BUILD__; diff --git a/packages/opentelemetry-node/src/index.ts b/packages/opentelemetry-node/src/index.ts deleted file mode 100644 index 72f9bf46d5e9..000000000000 --- a/packages/opentelemetry-node/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { SentrySpanProcessor } from './spanprocessor'; -export { SentryPropagator } from './propagator'; -export { maybeCaptureExceptionForTimedEvent } from './utils/captureExceptionForTimedEvent'; -export { parseOtelSpanDescription } from './utils/parseOtelSpanDescription'; -export { mapOtelStatus } from './utils/mapOtelStatus'; diff --git a/packages/opentelemetry-node/src/propagator.ts b/packages/opentelemetry-node/src/propagator.ts deleted file mode 100644 index ead43afdb8f5..000000000000 --- a/packages/opentelemetry-node/src/propagator.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { Baggage, Context, TextMapGetter, TextMapSetter } from '@opentelemetry/api'; -import { TraceFlags, isSpanContextValid, propagation, trace } from '@opentelemetry/api'; -import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core'; -import { getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader } from '@sentry/core'; -import { - SENTRY_BAGGAGE_KEY_PREFIX, - baggageHeaderToDynamicSamplingContext, - extractTraceparentData, -} from '@sentry/utils'; - -import { - SENTRY_BAGGAGE_HEADER, - SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, - SENTRY_TRACE_HEADER, - SENTRY_TRACE_PARENT_CONTEXT_KEY, -} from './constants'; -import { getSentrySpan } from './utils/spanMap'; - -/** - * Injects and extracts `sentry-trace` and `baggage` headers from carriers. - */ -export class SentryPropagator extends W3CBaggagePropagator { - /** - * @inheritDoc - */ - public inject(context: Context, carrier: unknown, setter: TextMapSetter): void { - const spanContext = trace.getSpanContext(context); - if (!spanContext || !isSpanContextValid(spanContext) || isTracingSuppressed(context)) { - return; - } - - let baggage = propagation.getBaggage(context) || propagation.createBaggage({}); - - const span = getSentrySpan(spanContext.spanId); - if (span) { - setter.set(carrier, SENTRY_TRACE_HEADER, spanToTraceHeader(span)); - - if (getRootSpan(span)) { - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span); - baggage = Object.entries(dynamicSamplingContext).reduce((b, [dscKey, dscValue]) => { - if (dscValue) { - return b.setEntry(`${SENTRY_BAGGAGE_KEY_PREFIX}${dscKey}`, { value: dscValue }); - } - return b; - }, baggage); - } - } - super.inject(propagation.setBaggage(context, baggage), carrier, setter); - } - - /** - * @inheritDoc - */ - public extract(context: Context, carrier: unknown, getter: TextMapGetter): Context { - let newContext = context; - - const maybeSentryTraceHeader: string | string[] | undefined = getter.get(carrier, SENTRY_TRACE_HEADER); - if (maybeSentryTraceHeader) { - const header = Array.isArray(maybeSentryTraceHeader) ? maybeSentryTraceHeader[0] : maybeSentryTraceHeader; - const traceparentData = extractTraceparentData(header || ''); - newContext = newContext.setValue(SENTRY_TRACE_PARENT_CONTEXT_KEY, traceparentData); - if (traceparentData) { - const spanContext = { - traceId: traceparentData.traceId || '', - spanId: traceparentData.parentSpanId || '', - isRemote: true, - // Always sample if traceparent exists, we use SentrySpanProcessor to make sampling decisions with `startTransaction`. - traceFlags: TraceFlags.SAMPLED, - }; - newContext = trace.setSpanContext(newContext, spanContext); - } - } - - const maybeBaggageHeader = getter.get(carrier, SENTRY_BAGGAGE_HEADER); - const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(maybeBaggageHeader); - newContext = newContext.setValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, dynamicSamplingContext); - - return newContext; - } - - /** - * @inheritDoc - */ - public fields(): string[] { - return [SENTRY_TRACE_HEADER, SENTRY_BAGGAGE_HEADER]; - } -} diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts deleted file mode 100644 index 7788858c586d..000000000000 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ /dev/null @@ -1,234 +0,0 @@ -import type { Context } from '@opentelemetry/api'; -import { SpanKind, context, trace } from '@opentelemetry/api'; -import { suppressTracing } from '@opentelemetry/core'; -import type { Span as OtelSpan, SpanProcessor as OtelSpanProcessor } from '@opentelemetry/sdk-trace-base'; -import type { SentrySpan } from '@sentry/core'; -import { - SEMANTIC_ATTRIBUTE_SENTRY_OP, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - Transaction, - addEventProcessor, - addTracingExtensions, - getClient, - getCurrentHub, -} from '@sentry/core'; -import type { DynamicSamplingContext, TraceparentData, TransactionContext } from '@sentry/types'; -import { logger } from '@sentry/utils'; - -import { SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, SENTRY_TRACE_PARENT_CONTEXT_KEY } from './constants'; -import { DEBUG_BUILD } from './debug-build'; -import { maybeCaptureExceptionForTimedEvent } from './utils/captureExceptionForTimedEvent'; -import { isSentryRequestSpan } from './utils/isSentryRequest'; -import { mapOtelStatus } from './utils/mapOtelStatus'; -import { parseOtelSpanDescription } from './utils/parseOtelSpanDescription'; -import { clearSpan, getSentrySpan, setSentrySpan } from './utils/spanMap'; - -/** - * Converts OpenTelemetry Spans to Sentry Spans and sends them to Sentry via - * the Sentry SDK. - */ -export class SentrySpanProcessor implements OtelSpanProcessor { - public constructor() { - addTracingExtensions(); - - addEventProcessor(event => { - const otelSpan = trace && trace.getActiveSpan && (trace.getActiveSpan() as OtelSpan | undefined); - if (!otelSpan) { - return event; - } - - const otelSpanContext = otelSpan.spanContext(); - - // If event has already set `trace` context, use that one. - event.contexts = { - trace: { - trace_id: otelSpanContext.traceId, - span_id: otelSpanContext.spanId, - parent_span_id: otelSpan.parentSpanId, - }, - ...event.contexts, - }; - - return event; - }); - } - - /** - * @inheritDoc - */ - public onStart(otelSpan: OtelSpan, parentContext: Context): void { - const otelSpanId = otelSpan.spanContext().spanId; - const otelParentSpanId = otelSpan.parentSpanId; - - // Otel supports having multiple non-nested spans at the same time - // so we cannot use hub.getSpan(), as we cannot rely on this being on the current span - const sentryParentSpan = otelParentSpanId && getSentrySpan(otelParentSpanId); - - if (sentryParentSpan) { - // eslint-disable-next-line deprecation/deprecation - const sentryChildSpan = sentryParentSpan.startChild({ - name: otelSpan.name, - startTimestamp: convertOtelTimeToSeconds(otelSpan.startTime), - spanId: otelSpanId, - }) as SentrySpan; - - setSentrySpan(otelSpanId, sentryChildSpan); - } else { - const traceCtx = getTraceData(otelSpan, parentContext); - // eslint-disable-next-line deprecation/deprecation - const transaction = getCurrentHub().startTransaction({ - name: otelSpan.name, - ...traceCtx, - attributes: otelSpan.attributes, - startTimestamp: convertOtelTimeToSeconds(otelSpan.startTime), - spanId: otelSpanId, - }); - - setSentrySpan(otelSpanId, transaction as unknown as SentrySpan); - } - } - - /** - * @inheritDoc - */ - public onEnd(otelSpan: OtelSpan): void { - const otelSpanId = otelSpan.spanContext().spanId; - const sentrySpan = getSentrySpan(otelSpanId); - - if (!sentrySpan) { - DEBUG_BUILD && logger.error(`SentrySpanProcessor could not find span with OTEL-spanId ${otelSpanId} to finish.`); - clearSpan(otelSpanId); - return; - } - - // Auto-instrumentation often captures outgoing HTTP requests - // This means that Sentry HTTP requests created by this integration can, in turn, be captured by OTEL auto instrumentation, - // 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)) { - clearSpan(otelSpanId); - return; - } - - const client = getClient(); - - const mutableOptions = { drop: false }; - client && client.emit('otelSpanEnd', otelSpan, mutableOptions); - - if (mutableOptions.drop) { - clearSpan(otelSpanId); - return; - } - - // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub(); - otelSpan.events.forEach(event => { - maybeCaptureExceptionForTimedEvent(hub, event, otelSpan); - }); - - if (sentrySpan instanceof Transaction) { - updateTransactionWithOtelData(sentrySpan, otelSpan); - sentrySpan.setHub(hub); - } else { - updateSpanWithOtelData(sentrySpan, otelSpan); - } - - // Ensure we do not capture any OTEL spans for finishing (and sending) this - context.with(suppressTracing(context.active()), () => { - sentrySpan.end(convertOtelTimeToSeconds(otelSpan.endTime)); - }); - - clearSpan(otelSpanId); - } - - /** - * @inheritDoc - */ - public shutdown(): Promise { - return Promise.resolve(); - } - - /** - * @inheritDoc - */ - public async forceFlush(): Promise { - const client = getClient(); - if (client) { - return client.flush().then(); - } - return Promise.resolve(); - } -} - -function getTraceData(otelSpan: OtelSpan, parentContext: Context): Partial { - const spanContext = otelSpan.spanContext(); - const traceId = spanContext.traceId; - const spanId = spanContext.spanId; - - const parentSpanId = otelSpan.parentSpanId; - const traceparentData = parentContext.getValue(SENTRY_TRACE_PARENT_CONTEXT_KEY) as TraceparentData | undefined; - const dynamicSamplingContext = parentContext.getValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY) as - | Partial - | undefined; - - const context: Partial = { - spanId, - traceId, - parentSpanId, - metadata: { - // only set dynamic sampling context if sentry-trace header was set - dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, - }, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - }, - }; - - // Only inherit sample rate if `traceId` is the same - if (traceparentData && traceId === traceparentData.traceId) { - context.parentSampled = traceparentData.parentSampled; - } - - return context; -} - -function updateSpanWithOtelData(sentrySpan: SentrySpan, otelSpan: OtelSpan): void { - const { attributes, kind } = otelSpan; - - const { op, description, data } = parseOtelSpanDescription(otelSpan); - - sentrySpan.setStatus(mapOtelStatus(otelSpan)); - - const allData = { - ...attributes, - ...data, - 'otel.kind': SpanKind[kind], - }; - sentrySpan.setAttributes(allData); - - sentrySpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, op); - sentrySpan.updateName(description); -} - -function updateTransactionWithOtelData(transaction: Transaction, otelSpan: OtelSpan): void { - const { op, description, source, data } = parseOtelSpanDescription(otelSpan); - - // eslint-disable-next-line deprecation/deprecation - transaction.setContext('otel', { - attributes: otelSpan.attributes, - resource: otelSpan.resource.attributes, - }); - - const allData = data || {}; - transaction.setAttributes(allData); - - transaction.setStatus(mapOtelStatus(otelSpan)); - - transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, op); - transaction.updateName(description); - transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source); -} - -function convertOtelTimeToSeconds([seconds, nano]: [number, number]): number { - return seconds + nano / 1_000_000_000; -} diff --git a/packages/opentelemetry-node/src/utils/captureExceptionForTimedEvent.ts b/packages/opentelemetry-node/src/utils/captureExceptionForTimedEvent.ts deleted file mode 100644 index dcf02b671044..000000000000 --- a/packages/opentelemetry-node/src/utils/captureExceptionForTimedEvent.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { Span as OtelSpan, TimedEvent } from '@opentelemetry/sdk-trace-base'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import type { Hub } from '@sentry/types'; -import { isString } from '@sentry/utils'; - -/** - * Maybe capture a Sentry exception for an OTEL timed event. - * This will check if the event is exception-like and in that case capture it as an exception. - */ -export function maybeCaptureExceptionForTimedEvent(hub: Hub, event: TimedEvent, otelSpan?: OtelSpan): void { - if (event.name !== 'exception') { - return; - } - - const attributes = event.attributes; - if (!attributes) { - return; - } - - const message = attributes[SemanticAttributes.EXCEPTION_MESSAGE]; - - if (typeof message !== 'string') { - return; - } - - const syntheticError = new Error(message); - - const stack = attributes[SemanticAttributes.EXCEPTION_STACKTRACE]; - if (isString(stack)) { - syntheticError.stack = stack; - } - - const type = attributes[SemanticAttributes.EXCEPTION_TYPE]; - if (isString(type)) { - syntheticError.name = type; - } - - // eslint-disable-next-line deprecation/deprecation - hub.captureException(syntheticError, { - captureContext: otelSpan - ? { - contexts: { - otel: { - attributes: otelSpan.attributes, - resource: otelSpan.resource.attributes, - }, - trace: { - trace_id: otelSpan.spanContext().traceId, - span_id: otelSpan.spanContext().spanId, - parent_span_id: otelSpan.parentSpanId, - }, - }, - } - : undefined, - }); -} diff --git a/packages/opentelemetry-node/src/utils/isSentryRequest.ts b/packages/opentelemetry-node/src/utils/isSentryRequest.ts deleted file mode 100644 index 85cb6c9c77b9..000000000000 --- a/packages/opentelemetry-node/src/utils/isSentryRequest.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import { getClient, isSentryRequestUrl } from '@sentry/core'; - -/** - * - * @param otelSpan Checks wheter a given OTEL Span is an http request to sentry. - * @returns boolean - */ -export function isSentryRequestSpan(otelSpan: OtelSpan): boolean { - const { attributes } = otelSpan; - - const httpUrl = attributes[SemanticAttributes.HTTP_URL]; - - if (!httpUrl) { - return false; - } - - return isSentryRequestUrl(httpUrl.toString(), getClient()); -} diff --git a/packages/opentelemetry-node/src/utils/mapOtelStatus.ts b/packages/opentelemetry-node/src/utils/mapOtelStatus.ts deleted file mode 100644 index 5a75db2a9eea..000000000000 --- a/packages/opentelemetry-node/src/utils/mapOtelStatus.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { SpanStatusCode } from '@opentelemetry/api'; -import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import { SPAN_STATUS_ERROR, SPAN_STATUS_OK } from '@sentry/core'; -import type { SpanStatus } from '@sentry/types'; - -// canonicalCodesHTTPMap maps some HTTP codes to Sentry's span statuses. See possible mapping in https://develop.sentry.dev/sdk/event-payloads/span/ -const canonicalCodesHTTPMap: Record = { - '400': 'failed_precondition', - '401': 'unauthenticated', - '403': 'permission_denied', - '404': 'not_found', - '409': 'aborted', - '429': 'resource_exhausted', - '499': 'cancelled', - '500': 'internal_error', - '501': 'unimplemented', - '503': 'unavailable', - '504': 'deadline_exceeded', -} as const; - -// canonicalCodesGrpcMap maps some GRPC codes to Sentry's span statuses. See description in grpc documentation. -const canonicalCodesGrpcMap: Record = { - '1': 'cancelled', - '2': 'unknown_error', - '3': 'invalid_argument', - '4': 'deadline_exceeded', - '5': 'not_found', - '6': 'already_exists', - '7': 'permission_denied', - '8': 'resource_exhausted', - '9': 'failed_precondition', - '10': 'aborted', - '11': 'out_of_range', - '12': 'unimplemented', - '13': 'internal_error', - '14': 'unavailable', - '15': 'data_loss', - '16': 'unauthenticated', -} as const; - -/** - * Get a Sentry span status from an otel span. - * - * @param otelSpan An otel span to generate a sentry status for. - * @returns The Sentry span status - */ -export function mapOtelStatus(otelSpan: OtelSpan): SpanStatus { - const { status, attributes } = otelSpan; - - const httpCode = attributes[SemanticAttributes.HTTP_STATUS_CODE]; - const grpcCode = attributes[SemanticAttributes.RPC_GRPC_STATUS_CODE]; - - const code = typeof httpCode === 'string' ? httpCode : typeof httpCode === 'number' ? httpCode.toString() : undefined; - if (code) { - const sentryStatus = canonicalCodesHTTPMap[code]; - if (sentryStatus) { - return { code: SPAN_STATUS_ERROR, message: sentryStatus }; - } - } - - if (typeof grpcCode === 'string') { - const sentryStatus = canonicalCodesGrpcMap[grpcCode]; - if (sentryStatus) { - return { code: SPAN_STATUS_ERROR, message: sentryStatus }; - } - } - - const statusCode = status && status.code; - if (statusCode === SpanStatusCode.OK || statusCode === SpanStatusCode.UNSET) { - return { code: SPAN_STATUS_OK }; - } - - return { code: SPAN_STATUS_ERROR, message: 'unknown_error' }; -} diff --git a/packages/opentelemetry-node/src/utils/parseOtelSpanDescription.ts b/packages/opentelemetry-node/src/utils/parseOtelSpanDescription.ts deleted file mode 100644 index d53d31aa2069..000000000000 --- a/packages/opentelemetry-node/src/utils/parseOtelSpanDescription.ts +++ /dev/null @@ -1,165 +0,0 @@ -import type { AttributeValue, Attributes } from '@opentelemetry/api'; -import { SpanKind } from '@opentelemetry/api'; -import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import type { TransactionSource } from '@sentry/types'; -import { getSanitizedUrlString, parseUrl, stripUrlQueryAndFragment } from '@sentry/utils'; - -interface SpanDescription { - op: string | undefined; - description: string; - source: TransactionSource; - data?: Record; -} - -/** - * Extract better op/description from an otel span. - * - * Based on https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/7422ce2a06337f68a59b552b8c5a2ac125d6bae5/exporter/sentryexporter/sentry_exporter.go#L306 - * - * @param otelSpan - * @returns Better op/description to use, or undefined - */ -export function parseOtelSpanDescription(otelSpan: OtelSpan): SpanDescription { - const { attributes, name } = otelSpan; - - // if http.method exists, this is an http request span - const httpMethod = attributes[SemanticAttributes.HTTP_METHOD]; - if (httpMethod) { - return descriptionForHttpMethod(otelSpan, httpMethod); - } - - // If db.type exists then this is a database call span. - const dbSystem = attributes[SemanticAttributes.DB_SYSTEM]; - if (dbSystem) { - return descriptionForDbSystem(otelSpan, dbSystem); - } - - // If rpc.service exists then this is a rpc call span. - const rpcService = attributes[SemanticAttributes.RPC_SERVICE]; - if (rpcService) { - return { - op: 'rpc', - description: name, - source: 'route', - }; - } - - // If messaging.system exists then this is a messaging system span. - const messagingSystem = attributes[SemanticAttributes.MESSAGING_SYSTEM]; - if (messagingSystem) { - return { - op: 'message', - description: name, - source: 'route', - }; - } - - // If faas.trigger exists then this is a function as a service span. - const faasTrigger = attributes[SemanticAttributes.FAAS_TRIGGER]; - if (faasTrigger) { - return { op: faasTrigger.toString(), description: name, source: 'route' }; - } - - return { op: undefined, description: name, source: 'custom' }; -} - -function descriptionForDbSystem(otelSpan: OtelSpan, _dbSystem: AttributeValue): SpanDescription { - const { attributes, name } = otelSpan; - - // Use DB statement (Ex "SELECT * FROM table") if possible as description. - const statement = attributes[SemanticAttributes.DB_STATEMENT]; - - const description = statement ? statement.toString() : name; - - return { op: 'db', description, source: 'task' }; -} - -function descriptionForHttpMethod(otelSpan: OtelSpan, httpMethod: AttributeValue): SpanDescription { - const { name, kind, attributes } = otelSpan; - - const opParts = ['http']; - - switch (kind) { - case SpanKind.CLIENT: - opParts.push('client'); - break; - case SpanKind.SERVER: - opParts.push('server'); - break; - } - - const httpRoute = attributes[SemanticAttributes.HTTP_ROUTE]; - const { urlPath, url, query, fragment } = getSanitizedUrl(attributes, kind); - - if (!urlPath) { - return { op: opParts.join('.'), description: name, source: 'custom' }; - } - - // Ex. description="GET /api/users". - const description = `${httpMethod} ${urlPath}`; - - // If `httpPath` is a root path, then we can categorize the transaction source as route. - const source: TransactionSource = httpRoute || urlPath === '/' ? 'route' : 'url'; - - const data: Record = {}; - - if (url) { - data.url = url; - } - if (query) { - data['http.query'] = query; - } - if (fragment) { - data['http.fragment'] = fragment; - } - - return { - op: opParts.join('.'), - description, - source, - data, - }; -} - -/** Exported for tests only */ -export function getSanitizedUrl( - attributes: Attributes, - kind: SpanKind, -): { - url: string | undefined; - urlPath: string | undefined; - query: string | undefined; - fragment: string | undefined; -} { - // This is the relative path of the URL, e.g. /sub - const httpTarget = attributes[SemanticAttributes.HTTP_TARGET]; - // This is the full URL, including host & query params etc., e.g. https://example.com/sub?foo=bar - const httpUrl = attributes[SemanticAttributes.HTTP_URL]; - // This is the normalized route name - may not always be available! - const httpRoute = attributes[SemanticAttributes.HTTP_ROUTE]; - - const parsedUrl = typeof httpUrl === 'string' ? parseUrl(httpUrl) : undefined; - const url = parsedUrl ? getSanitizedUrlString(parsedUrl) : undefined; - const query = parsedUrl && parsedUrl.search ? parsedUrl.search : undefined; - const fragment = parsedUrl && parsedUrl.hash ? parsedUrl.hash : undefined; - - if (typeof httpRoute === 'string') { - return { urlPath: httpRoute, url, query, fragment }; - } - - if (kind === SpanKind.SERVER && typeof httpTarget === 'string') { - return { urlPath: stripUrlQueryAndFragment(httpTarget), url, query, fragment }; - } - - if (parsedUrl) { - return { urlPath: url, url, query, fragment }; - } - - // fall back to target even for client spans, if no URL is present - if (typeof httpTarget === 'string') { - return { urlPath: stripUrlQueryAndFragment(httpTarget), url, query, fragment }; - } - - return { urlPath: undefined, url, query, fragment }; -} diff --git a/packages/opentelemetry-node/src/utils/spanMap.ts b/packages/opentelemetry-node/src/utils/spanMap.ts deleted file mode 100644 index 49e4c033403e..000000000000 --- a/packages/opentelemetry-node/src/utils/spanMap.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { SentrySpan } from '@sentry/core'; -import { getRootSpan } from '@sentry/core'; - -interface SpanMapEntry { - sentrySpan: SentrySpan; - ref: SpanRefType; - // These are not direct children, but all spans under the tree of a root span. - subSpans: string[]; -} - -const SPAN_REF_ROOT = Symbol('root'); -const SPAN_REF_CHILD = Symbol('child'); -const SPAN_REF_CHILD_ENDED = Symbol('child_ended'); -type SpanRefType = typeof SPAN_REF_ROOT | typeof SPAN_REF_CHILD | typeof SPAN_REF_CHILD_ENDED; - -/** Exported only for tests. */ -export const SPAN_MAP = new Map(); - -/** - * Get a Sentry span for a given span ID. - */ -export function getSentrySpan(spanId: string): SentrySpan | undefined { - const entry = SPAN_MAP.get(spanId); - return entry ? entry.sentrySpan : undefined; -} - -/** - * Set a Sentry span for a given span ID. - * This is necessary so we can lookup parent spans later. - * We also keep a list of children for root spans only, in order to be able to clean them up together. - */ -export function setSentrySpan(spanId: string, sentrySpan: SentrySpan): void { - let ref: SpanRefType = SPAN_REF_ROOT; - - const rootSpanId = getRootSpan(sentrySpan)?.spanContext().spanId; - - if (rootSpanId && rootSpanId !== spanId) { - const root = SPAN_MAP.get(rootSpanId); - if (root) { - root.subSpans.push(spanId); - ref = SPAN_REF_CHILD; - } - } - - SPAN_MAP.set(spanId, { - sentrySpan, - ref, - subSpans: [], - }); -} - -/** - * Clear references of the given span ID. - */ -export function clearSpan(spanId: string): void { - const entry = SPAN_MAP.get(spanId); - if (!entry) { - return; - } - - const { ref, subSpans } = entry; - - // If this is a child, mark it as ended. - if (ref === SPAN_REF_CHILD) { - entry.ref = SPAN_REF_CHILD_ENDED; - return; - } - - // If this is a root span, clear all (ended) children - if (ref === SPAN_REF_ROOT) { - for (const childId of subSpans) { - const child = SPAN_MAP.get(childId); - if (!child) { - continue; - } - - if (child.ref === SPAN_REF_CHILD_ENDED) { - // if the child has already ended, just clear it - SPAN_MAP.delete(childId); - } else if (child.ref === SPAN_REF_CHILD) { - // If the child has not ended yet, mark it as a root span so it is cleared when it ends. - child.ref = SPAN_REF_ROOT; - } - } - - SPAN_MAP.delete(spanId); - return; - } - - // Generally, `clearSpan` should never be called for ref === SPAN_REF_CHILD_ENDED - // But if it does, just clear the span - SPAN_MAP.delete(spanId); -} diff --git a/packages/opentelemetry-node/test/propagator.test.ts b/packages/opentelemetry-node/test/propagator.test.ts deleted file mode 100644 index 550ec2633843..000000000000 --- a/packages/opentelemetry-node/test/propagator.test.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { - ROOT_CONTEXT, - TraceFlags, - defaultTextMapGetter, - defaultTextMapSetter, - propagation, - trace, -} from '@opentelemetry/api'; -import { suppressTracing } from '@opentelemetry/core'; -import type { SentrySpan } from '@sentry/core'; -import { Transaction, addTracingExtensions, getCurrentHub, setCurrentClient } from '@sentry/core'; -import type { Client, TransactionContext } from '@sentry/types'; - -import { - SENTRY_BAGGAGE_HEADER, - SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, - SENTRY_TRACE_HEADER, - SENTRY_TRACE_PARENT_CONTEXT_KEY, -} from '../src/constants'; -import { SentryPropagator } from '../src/propagator'; -import { SPAN_MAP, setSentrySpan } from '../src/utils/spanMap'; - -beforeAll(() => { - addTracingExtensions(); -}); - -describe('SentryPropagator', () => { - const propagator = new SentryPropagator(); - let carrier: { [key: string]: unknown }; - - beforeEach(() => { - carrier = {}; - }); - - it('returns fields set', () => { - expect(propagator.fields()).toEqual([SENTRY_TRACE_HEADER, SENTRY_BAGGAGE_HEADER]); - }); - - describe('inject', () => { - describe('baggage and sentry-trace', () => { - const client = { - getOptions: () => ({ - environment: 'production', - release: '1.0.0', - }), - getDsn: () => ({ - publicKey: 'abc', - }), - emit: () => {}, - } as unknown as Client; - - setCurrentClient(client); - - afterEach(() => { - SPAN_MAP.clear(); - }); - - enum PerfType { - Transaction = 'transaction', - Span = 'span', - } - - function createTransactionAndMaybeSpan(type: PerfType, transactionContext: TransactionContext) { - // eslint-disable-next-line deprecation/deprecation - const transaction = new Transaction(transactionContext, getCurrentHub()); - setSentrySpan(transaction.spanContext().spanId, transaction); - if (type === PerfType.Span) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { spanId, ...ctx } = transactionContext; - // eslint-disable-next-line deprecation/deprecation - const span = transaction.startChild({ ...ctx, name: transactionContext.name }) as SentrySpan; - setSentrySpan(span.spanContext().spanId, span); - } - } - - describe.each([PerfType.Transaction, PerfType.Span])('with active %s', type => { - it.each([ - [ - 'should set baggage and header when sampled', - { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }, - { - name: 'sampled-transaction', - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - sampled: true, - }, - 'sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b,sentry-transaction=sampled-transaction,sentry-sampled=true', - 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1', - ], - [ - 'should NOT set baggage header when not sampled', - { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.NONE, - }, - { - name: 'not-sampled-transaction', - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - sampled: false, - }, - 'sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b,sentry-transaction=not-sampled-transaction,sentry-sampled=false', - 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-0', - ], - [ - 'should NOT set baggage header when traceId is empty', - { - traceId: '', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }, - { - name: 'empty-traceId-transaction', - traceId: '', - spanId: '6e0c63257de34c92', - sampled: true, - }, - undefined, - undefined, - ], - [ - 'should NOT set baggage header when spanId is empty', - { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '', - traceFlags: TraceFlags.SAMPLED, - }, - { - name: 'empty-spanId-transaction', - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '', - sampled: true, - }, - undefined, - undefined, - ], - ])('%s', (_name, spanContext, transactionContext, baggage, sentryTrace) => { - createTransactionAndMaybeSpan(type, transactionContext); - const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); - propagator.inject(context, carrier, defaultTextMapSetter); - expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(baggage); - expect(carrier[SENTRY_TRACE_HEADER]).toBe(sentryTrace); - }); - - it('should include existing baggage', () => { - const transactionContext = { - name: 'sampled-transaction', - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - sampled: true, - }; - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }; - createTransactionAndMaybeSpan(type, transactionContext); - const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); - const baggage = propagation.createBaggage({ foo: { value: 'bar' } }); - propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter); - expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe( - 'foo=bar,sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b,sentry-transaction=sampled-transaction,sentry-sampled=true', - ); - }); - - it('should create baggage without active transaction', () => { - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }; - const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); - const baggage = propagation.createBaggage({ foo: { value: 'bar' } }); - propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter); - expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe('foo=bar'); - }); - - it('should NOT set baggage and sentry-trace header if instrumentation is supressed', () => { - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }; - const transactionContext = { - name: 'sampled-transaction', - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - sampled: true, - }; - createTransactionAndMaybeSpan(type, transactionContext); - const context = suppressTracing(trace.setSpanContext(ROOT_CONTEXT, spanContext)); - propagator.inject(context, carrier, defaultTextMapSetter); - expect(carrier[SENTRY_TRACE_HEADER]).toBe(undefined); - expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(undefined); - }); - }); - }); - }); - - describe('extract', () => { - it('sets sentry span context on the context', () => { - const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'; - carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(trace.getSpanContext(context)).toEqual({ - isRemote: true, - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - }); - }); - - it('sets defined sentry trace header on context', () => { - const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'; - carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(context.getValue(SENTRY_TRACE_PARENT_CONTEXT_KEY)).toEqual({ - parentSampled: true, - parentSpanId: '6e0c63257de34c92', - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - }); - }); - - it('sets undefined sentry trace header on context', () => { - const sentryTraceHeader = undefined; - carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(context.getValue(SENTRY_TRACE_PARENT_CONTEXT_KEY)).toEqual(undefined); - }); - - it('sets defined dynamic sampling context on context', () => { - const baggage = - 'sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b,sentry-transaction=dsc-transaction'; - carrier[SENTRY_BAGGAGE_HEADER] = baggage; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(context.getValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY)).toEqual({ - environment: 'production', - public_key: 'abc', - release: '1.0.0', - trace_id: 'd4cda95b652f4a1592b449d5929fda1b', - transaction: 'dsc-transaction', - }); - }); - - it('sets undefined dynamic sampling context on context', () => { - const baggage = ''; - carrier[SENTRY_BAGGAGE_HEADER] = baggage; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(context.getValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY)).toEqual(undefined); - }); - - it('handles when sentry-trace is an empty array', () => { - carrier[SENTRY_TRACE_HEADER] = []; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(context.getValue(SENTRY_TRACE_PARENT_CONTEXT_KEY)).toEqual(undefined); - }); - }); -}); diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts deleted file mode 100644 index c96c3032a822..000000000000 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ /dev/null @@ -1,1009 +0,0 @@ -import type * as OpenTelemetry from '@opentelemetry/api'; -import { SpanKind } from '@opentelemetry/api'; -import { Resource } from '@opentelemetry/resources'; -import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; -import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; -import { SemanticAttributes, SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; -import { captureException, getCurrentScope, setCurrentClient } from '@sentry/core'; -import { SentrySpan, Transaction, addTracingExtensions, createTransport, spanToJSON } from '@sentry/core'; -import { NodeClient } from '@sentry/node-experimental'; -import type { SpanStatus } from '@sentry/types'; -import { resolvedSyncPromise } from '@sentry/utils'; - -import { SentrySpanProcessor } from '../src/spanprocessor'; -import { SPAN_MAP, clearSpan, getSentrySpan } from '../src/utils/spanMap'; - -const SENTRY_DSN = 'https://0@0.ingest.sentry.io/0'; - -const DEFAULT_NODE_CLIENT_OPTIONS = { - dsn: SENTRY_DSN, - integrations: [], - transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})), - stackParser: () => [], -}; - -// Integration Test of SentrySpanProcessor - -beforeAll(() => { - addTracingExtensions(); -}); - -describe('SentrySpanProcessor', () => { - let client: NodeClient; - let provider: NodeTracerProvider; - let spanProcessor: SentrySpanProcessor; - - beforeEach(() => { - // To avoid test leakage, clear before each test - SPAN_MAP.clear(); - - client = new NodeClient(DEFAULT_NODE_CLIENT_OPTIONS); - setCurrentClient(client); - client.init(); - - spanProcessor = new SentrySpanProcessor(); - provider = new NodeTracerProvider({ - resource: new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: 'test-service', - }), - }); - provider.addSpanProcessor(spanProcessor); - provider.register(); - }); - - afterEach(async () => { - // Ensure test map is empty! - // Otherwise, we seem to have a leak somewhere... - expect(SPAN_MAP.size).toBe(0); - - await provider.forceFlush(); - await provider.shutdown(); - }); - - function getSpanForOtelSpan(otelSpan: OtelSpan | OpenTelemetry.Span) { - return getSentrySpan(otelSpan.spanContext().spanId); - } - - function getContext(transaction: Transaction) { - const transactionWithContext = transaction as unknown as Transaction; - // @ts-expect-error accessing private property - return transactionWithContext._contexts; - } - - it('creates a transaction', async () => { - const startTimestampMs = 1667381672309; - const endTimestampMs = 1667381672875; - const startTime = otelNumberToHrtime(startTimestampMs); - const endTime = otelNumberToHrtime(endTimestampMs); - - const otelSpan = provider.getTracer('default').startSpan('GET /users', { startTime }) as OtelSpan; - - const sentrySpanTransaction = getSpanForOtelSpan(otelSpan) as Transaction | undefined; - expect(sentrySpanTransaction).toBeInstanceOf(Transaction); - - expect(spanToJSON(sentrySpanTransaction!).description).toBe('GET /users'); - expect(spanToJSON(sentrySpanTransaction!).start_timestamp).toEqual(startTimestampMs / 1000); - expect(sentrySpanTransaction?.spanContext().traceId).toEqual(otelSpan.spanContext().traceId); - expect(spanToJSON(sentrySpanTransaction!).parent_span_id).toEqual(otelSpan.parentSpanId); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpanTransaction?.parentSpanId).toEqual(otelSpan.parentSpanId); - expect(sentrySpanTransaction?.spanContext().spanId).toEqual(otelSpan.spanContext().spanId); - - otelSpan.end(endTime); - - expect(spanToJSON(sentrySpanTransaction!).timestamp).toBe(endTimestampMs / 1000); - }); - - it('creates a child span if there is a running transaction', () => { - const startTimestampMs = 1667381672309; - const endTimestampMs = 1667381672875; - const startTime = otelNumberToHrtime(startTimestampMs); - const endTime = otelNumberToHrtime(endTimestampMs); - - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('SELECT * FROM users;', { startTime }, child => { - const childOtelSpan = child as OtelSpan; - - const sentrySpanTransaction = getSpanForOtelSpan(parentOtelSpan) as Transaction | undefined; - expect(sentrySpanTransaction).toBeInstanceOf(Transaction); - - const sentrySpan = getSpanForOtelSpan(childOtelSpan); - expect(sentrySpan).toBeInstanceOf(SentrySpan); - expect(sentrySpan ? spanToJSON(sentrySpan).description : undefined).toBe('SELECT * FROM users;'); - expect(spanToJSON(sentrySpan!).start_timestamp).toEqual(startTimestampMs / 1000); - expect(sentrySpan?.spanContext().spanId).toEqual(childOtelSpan.spanContext().spanId); - - expect(spanToJSON(sentrySpan!).parent_span_id).toEqual(sentrySpanTransaction?.spanContext().spanId); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.parentSpanId).toEqual(sentrySpanTransaction?.spanContext().spanId); - - // eslint-disable-next-line deprecation/deprecation - expect(getCurrentScope().getSpan()).toBeUndefined(); - - child.end(endTime); - - expect(spanToJSON(sentrySpan!).timestamp).toEqual(endTimestampMs / 1000); - }); - - parentOtelSpan.end(); - }); - }); - - it('handles a missing parent reference', () => { - const startTimestampMs = 1667381672309; - const endTimestampMs = 1667381672875; - const startTime = otelNumberToHrtime(startTimestampMs); - const endTime = otelNumberToHrtime(endTimestampMs); - - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - // We simulate the parent somehow not existing in our internal map - // this can happen if a race condition leads to spans being processed out of order - clearSpan(parentOtelSpan.spanContext().spanId); - - tracer.startActiveSpan('SELECT * FROM users;', { startTime }, child => { - const childOtelSpan = child as OtelSpan; - - // Parent span does not exist... - const sentrySpanTransaction = getSpanForOtelSpan(parentOtelSpan); - expect(sentrySpanTransaction).toBeUndefined(); - - // Span itself exists and is created as transaction - const sentrySpan = getSpanForOtelSpan(childOtelSpan); - expect(sentrySpan).toBeInstanceOf(SentrySpan); - expect(sentrySpan).toBeInstanceOf(Transaction); - expect(spanToJSON(sentrySpan!).description).toBe('SELECT * FROM users;'); - expect(spanToJSON(sentrySpan!).start_timestamp).toEqual(startTimestampMs / 1000); - expect(sentrySpan?.spanContext().spanId).toEqual(childOtelSpan.spanContext().spanId); - - expect(spanToJSON(sentrySpan!).parent_span_id).toEqual(parentOtelSpan.spanContext().spanId); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.parentSpanId).toEqual(parentOtelSpan.spanContext().spanId); - - // eslint-disable-next-line deprecation/deprecation - expect(getCurrentScope().getSpan()).toBeUndefined(); - - child.end(endTime); - - expect(spanToJSON(sentrySpan!).timestamp).toEqual(endTimestampMs / 1000); - }); - - parentOtelSpan.end(); - }); - }); - - it('allows to create multiple child spans on same level', () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - const sentrySpanTransaction = getSpanForOtelSpan(parentOtelSpan) as Transaction | undefined; - - expect(sentrySpanTransaction).toBeInstanceOf(SentrySpan); - expect(spanToJSON(sentrySpanTransaction!).description).toBe('GET /users'); - - // Create some parallel, independent spans - const span1 = tracer.startSpan('SELECT * FROM users;') as OtelSpan; - const span2 = tracer.startSpan('SELECT * FROM companies;') as OtelSpan; - const span3 = tracer.startSpan('SELECT * FROM locations;') as OtelSpan; - - const sentrySpan1 = getSpanForOtelSpan(span1); - const sentrySpan2 = getSpanForOtelSpan(span2); - const sentrySpan3 = getSpanForOtelSpan(span3); - - expect(spanToJSON(sentrySpan1!).parent_span_id).toEqual(sentrySpanTransaction?.spanContext().spanId); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan1?.parentSpanId).toEqual(sentrySpanTransaction?.spanContext().spanId); - - expect(spanToJSON(sentrySpan2!).parent_span_id).toEqual(sentrySpanTransaction?.spanContext().spanId); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan2?.parentSpanId).toEqual(sentrySpanTransaction?.spanContext().spanId); - - expect(spanToJSON(sentrySpan3!).parent_span_id).toEqual(sentrySpanTransaction?.spanContext().spanId); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan3?.parentSpanId).toEqual(sentrySpanTransaction?.spanContext().spanId); - - expect(spanToJSON(sentrySpan1!).description).toEqual('SELECT * FROM users;'); - expect(spanToJSON(sentrySpan2!).description).toEqual('SELECT * FROM companies;'); - expect(spanToJSON(sentrySpan3!).description).toEqual('SELECT * FROM locations;'); - - span1.end(); - span2.end(); - span3.end(); - - parentOtelSpan.end(); - }); - }); - - it('handles child spans finished out of order', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parent => { - tracer.startActiveSpan('SELECT * FROM users;', child => { - const grandchild = tracer.startSpan('child 1'); - - const parentSpan = getSpanForOtelSpan(parent); - const childSpan = getSpanForOtelSpan(child); - const grandchildSpan = getSpanForOtelSpan(grandchild); - - parent.end(); - child.end(); - grandchild.end(); - - expect(parentSpan).toBeDefined(); - expect(childSpan).toBeDefined(); - expect(grandchildSpan).toBeDefined(); - - expect(spanToJSON(parentSpan!).timestamp).toBeDefined(); - expect(spanToJSON(childSpan!).timestamp).toBeDefined(); - expect(spanToJSON(grandchildSpan!).timestamp).toBeDefined(); - }); - }); - }); - - it('handles finished parent span before child span starts', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parent => { - const parentSpan = getSpanForOtelSpan(parent); - - parent.end(); - - tracer.startActiveSpan('SELECT * FROM users;', child => { - const childSpan = getSpanForOtelSpan(child); - - child.end(); - - expect(parentSpan).toBeDefined(); - expect(childSpan).toBeDefined(); - expect(parentSpan).toBeInstanceOf(Transaction); - expect(childSpan).toBeInstanceOf(Transaction); - expect(spanToJSON(parentSpan!).timestamp).toBeDefined(); - expect(spanToJSON(childSpan!).timestamp).toBeDefined(); - expect(spanToJSON(parentSpan!).parent_span_id).toBeUndefined(); - - expect(spanToJSON(parentSpan!).parent_span_id).toBeUndefined(); - // eslint-disable-next-line deprecation/deprecation - expect(parentSpan?.parentSpanId).toBeUndefined(); - - expect(spanToJSON(childSpan!).parent_span_id).toEqual(parentSpan?.spanContext().spanId); - // eslint-disable-next-line deprecation/deprecation - expect(childSpan?.parentSpanId).toEqual(parentSpan?.spanContext().spanId); - }); - }); - }); - - it('sets context for transaction', async () => { - const otelSpan = provider.getTracer('default').startSpan('GET /users'); - - const transaction = getSpanForOtelSpan(otelSpan) as Transaction; - - // context is only set after end - expect(getContext(transaction)).toEqual({}); - - otelSpan.end(); - - expect(getContext(transaction)).toEqual({ - otel: { - attributes: {}, - resource: { - 'service.name': 'test-service', - 'telemetry.sdk.language': 'nodejs', - 'telemetry.sdk.name': 'opentelemetry', - 'telemetry.sdk.version': '1.21.0', - }, - }, - }); - - // Start new transaction - const otelSpan2 = provider.getTracer('default').startSpan('GET /companies'); - - const transaction2 = getSpanForOtelSpan(otelSpan2) as Transaction; - - expect(getContext(transaction2)).toEqual({}); - - otelSpan2.setAttribute('test-attribute', 'test-value'); - - otelSpan2.end(); - - expect(getContext(transaction2)).toEqual({ - otel: { - attributes: { - 'test-attribute': 'test-value', - }, - resource: { - 'service.name': 'test-service', - 'telemetry.sdk.language': 'nodejs', - 'telemetry.sdk.name': 'opentelemetry', - 'telemetry.sdk.version': '1.21.0', - }, - }, - }); - }); - - it('sets data for span', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('SELECT * FROM users;', child => { - child.setAttribute('test-attribute', 'test-value'); - child.setAttribute('test-attribute-2', [1, 2, 3]); - child.setAttribute('test-attribute-3', 0); - child.setAttribute('test-attribute-4', false); - - const sentrySpan = getSpanForOtelSpan(child); - - // origin is set by default to 'manual' - expect(spanToJSON(sentrySpan!).data).toEqual({ 'sentry.origin': 'manual' }); - - child.end(); - - expect(spanToJSON(sentrySpan!).data).toEqual({ - 'otel.kind': 'INTERNAL', - 'test-attribute': 'test-value', - 'test-attribute-2': [1, 2, 3], - 'test-attribute-3': 0, - 'test-attribute-4': false, - 'sentry.origin': 'manual', - }); - }); - - parentOtelSpan.end(); - }); - }); - - it('sets status for transaction', async () => { - const otelSpan = provider.getTracer('default').startSpan('GET /users'); - - const transaction = getSpanForOtelSpan(otelSpan) as Transaction; - - // status is only set after end - expect(spanToJSON(transaction!).status).toBe(undefined); - - otelSpan.end(); - - expect(spanToJSON(transaction!).status).toBe('ok'); - }); - - it('sets status for span', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('SELECT * FROM users;', child => { - const sentrySpan = getSpanForOtelSpan(child); - - expect(spanToJSON(sentrySpan!).status).toBe(undefined); - - child.end(); - - expect(spanToJSON(sentrySpan!).status).toBe('ok'); - - parentOtelSpan.end(); - }); - }); - }); - - const statusTestTable: [number, undefined | number | string, undefined | string, SpanStatus['message']][] = [ - [-1, undefined, undefined, 'unknown_error'], - [3, undefined, undefined, 'unknown_error'], - [0, undefined, undefined, 'ok'], - [1, undefined, undefined, 'ok'], - [2, undefined, undefined, 'unknown_error'], - - // http codes - [2, 400, undefined, 'failed_precondition'], - [2, 401, undefined, 'unauthenticated'], - [2, 403, undefined, 'permission_denied'], - [2, 404, undefined, 'not_found'], - [2, 409, undefined, 'aborted'], - [2, 429, undefined, 'resource_exhausted'], - [2, 499, undefined, 'cancelled'], - [2, 500, undefined, 'internal_error'], - [2, 501, undefined, 'unimplemented'], - [2, 503, undefined, 'unavailable'], - [2, 504, undefined, 'deadline_exceeded'], - [2, 999, undefined, 'unknown_error'], - - [2, '400', undefined, 'failed_precondition'], - [2, '401', undefined, 'unauthenticated'], - [2, '403', undefined, 'permission_denied'], - [2, '404', undefined, 'not_found'], - [2, '409', undefined, 'aborted'], - [2, '429', undefined, 'resource_exhausted'], - [2, '499', undefined, 'cancelled'], - [2, '500', undefined, 'internal_error'], - [2, '501', undefined, 'unimplemented'], - [2, '503', undefined, 'unavailable'], - [2, '504', undefined, 'deadline_exceeded'], - [2, '999', undefined, 'unknown_error'], - - // grpc codes - [2, undefined, '1', 'cancelled'], - [2, undefined, '2', 'unknown_error'], - [2, undefined, '3', 'invalid_argument'], - [2, undefined, '4', 'deadline_exceeded'], - [2, undefined, '5', 'not_found'], - [2, undefined, '6', 'already_exists'], - [2, undefined, '7', 'permission_denied'], - [2, undefined, '8', 'resource_exhausted'], - [2, undefined, '9', 'failed_precondition'], - [2, undefined, '10', 'aborted'], - [2, undefined, '11', 'out_of_range'], - [2, undefined, '12', 'unimplemented'], - [2, undefined, '13', 'internal_error'], - [2, undefined, '14', 'unavailable'], - [2, undefined, '15', 'data_loss'], - [2, undefined, '16', 'unauthenticated'], - [2, undefined, '999', 'unknown_error'], - - // http takes precedence over grpc - [2, '400', '2', 'failed_precondition'], - ]; - - describe('convert otel span status', () => { - it.each(statusTestTable)( - 'works with otelStatus=%i, httpCode=%s, grpcCode=%s', - (otelStatus, httpCode, grpcCode, expected) => { - const otelSpan = provider.getTracer('default').startSpan('GET /users'); - const transaction = getSpanForOtelSpan(otelSpan) as Transaction; - - otelSpan.setStatus({ code: otelStatus }); - - if (httpCode) { - otelSpan.setAttribute(SemanticAttributes.HTTP_STATUS_CODE, httpCode); - } - - if (grpcCode) { - otelSpan.setAttribute(SemanticAttributes.RPC_GRPC_STATUS_CODE, grpcCode); - } - - otelSpan.end(); - expect(spanToJSON(transaction!).status).toBe(expected); - }, - ); - }); - - describe('update op/description', () => { - it('updates on end', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('SELECT * FROM users;', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.updateName('new name'); - - expect(sentrySpan && spanToJSON(sentrySpan).op).toBe(undefined); - expect(sentrySpan ? spanToJSON(sentrySpan).description : undefined).toBe('SELECT * FROM users;'); - - child.end(); - - expect(sentrySpan && spanToJSON(sentrySpan).op).toBe(undefined); - expect(sentrySpan ? spanToJSON(sentrySpan).description : undefined).toBe('new name'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for HTTP_METHOD for client', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('/users/all', { kind: SpanKind.CLIENT }, child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - - child.end(); - - expect(spanToJSON(sentrySpan!).op).toBe('http.client'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for HTTP_METHOD for server', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('/users/all', { kind: SpanKind.SERVER }, child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - - child.end(); - - expect(spanToJSON(sentrySpan!).op).toBe('http.server'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates op/description based on attributes for HTTP_METHOD without HTTP_ROUTE', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('HTTP GET', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - - child.end(); - - expect(sentrySpan ? spanToJSON(sentrySpan).description : undefined).toBe('HTTP GET'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for HTTP_METHOD with HTTP_ROUTE', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('HTTP GET', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - child.setAttribute(SemanticAttributes.HTTP_ROUTE, '/my/route/{id}'); - child.setAttribute(SemanticAttributes.HTTP_TARGET, '/my/route/123'); - child.setAttribute(SemanticAttributes.HTTP_URL, 'http://example.com/my/route/123'); - - child.end(); - - const { description, data } = spanToJSON(sentrySpan!); - - expect(description).toBe('GET /my/route/{id}'); - expect(data).toEqual({ - 'http.method': 'GET', - 'http.route': '/my/route/{id}', - 'http.target': '/my/route/123', - 'http.url': 'http://example.com/my/route/123', - 'otel.kind': 'INTERNAL', - url: 'http://example.com/my/route/123', - 'sentry.op': 'http', - 'sentry.origin': 'manual', - }); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for HTTP_METHOD with HTTP_TARGET', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('HTTP GET', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - child.setAttribute(SemanticAttributes.HTTP_TARGET, '/my/route/123'); - child.setAttribute(SemanticAttributes.HTTP_URL, 'http://example.com/my/route/123'); - - child.end(); - - const { description, data, op } = spanToJSON(sentrySpan!); - - expect(description).toBe('GET http://example.com/my/route/123'); - expect(data).toEqual({ - 'http.method': 'GET', - 'http.target': '/my/route/123', - 'http.url': 'http://example.com/my/route/123', - 'otel.kind': 'INTERNAL', - url: 'http://example.com/my/route/123', - 'sentry.op': 'http', - 'sentry.origin': 'manual', - }); - expect(op).toBe('http'); - - parentOtelSpan.end(); - }); - }); - }); - - it('Adds query & hash data based on HTTP_URL', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('HTTP GET', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - child.setAttribute(SemanticAttributes.HTTP_TARGET, '/my/route/123'); - child.setAttribute(SemanticAttributes.HTTP_URL, 'http://example.com/my/route/123?what=123#myHash'); - - child.end(); - - const { description, data, op } = spanToJSON(sentrySpan!); - - expect(description).toBe('GET http://example.com/my/route/123'); - expect(data).toEqual({ - 'http.method': 'GET', - 'http.target': '/my/route/123', - 'http.url': 'http://example.com/my/route/123?what=123#myHash', - 'otel.kind': 'INTERNAL', - url: 'http://example.com/my/route/123', - 'http.query': '?what=123', - 'http.fragment': '#myHash', - 'sentry.op': 'http', - 'sentry.origin': 'manual', - }); - expect(op).toBe('http'); - - parentOtelSpan.end(); - }); - }); - }); - - it('adds transaction source `url` for HTTP_TARGET', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', otelSpan => { - const sentrySpan = getSpanForOtelSpan(otelSpan); - - otelSpan.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - otelSpan.setAttribute(SemanticAttributes.HTTP_TARGET, '/my/route/123'); - - otelSpan.end(); - - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.transaction?.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('url'); - }); - }); - - it('adds transaction source `route` for root path HTTP_TARGET', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /', otelSpan => { - const sentrySpan = getSpanForOtelSpan(otelSpan); - - otelSpan.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - otelSpan.setAttribute(SemanticAttributes.HTTP_TARGET, '/'); - - otelSpan.end(); - - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.transaction?.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('route'); - }); - }); - - it('adds transaction source `url` for HTTP_ROUTE', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', otelSpan => { - const sentrySpan = getSpanForOtelSpan(otelSpan); - - otelSpan.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - otelSpan.setAttribute(SemanticAttributes.HTTP_ROUTE, '/my/route/:id'); - - otelSpan.end(); - - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.transaction?.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('route'); - }); - }); - - it('updates based on attributes for DB_SYSTEM', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('fetch users from DB', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.DB_SYSTEM, 'MySQL'); - child.setAttribute(SemanticAttributes.DB_STATEMENT, 'SELECT * FROM users'); - - child.end(); - - const { description, op } = spanToJSON(sentrySpan!); - expect(op).toBe('db'); - expect(description).toBe('SELECT * FROM users'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for DB_SYSTEM without DB_STATEMENT', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('fetch users from DB', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.DB_SYSTEM, 'MySQL'); - - child.end(); - - const { description, op } = spanToJSON(sentrySpan!); - expect(op).toBe('db'); - expect(description).toBe('fetch users from DB'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for RPC_SERVICE', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('test operation', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.RPC_SERVICE, 'rpc service'); - - child.end(); - - const { op, description } = spanToJSON(sentrySpan!); - expect(op).toBe('rpc'); - expect(description).toBe('test operation'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for MESSAGING_SYSTEM', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('test operation', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.MESSAGING_SYSTEM, 'messaging system'); - - child.end(); - - const { op, description } = spanToJSON(sentrySpan!); - expect(op).toBe('message'); - expect(description).toBe('test operation'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for FAAS_TRIGGER', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('test operation', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.FAAS_TRIGGER, 'test faas trigger'); - - child.end(); - - const { op, description } = spanToJSON(sentrySpan!); - expect(op).toBe('test faas trigger'); - expect(description).toBe('test operation'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates Sentry transaction', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('test operation', parentOtelSpan => { - const transaction = getSpanForOtelSpan(parentOtelSpan) as Transaction; - - parentOtelSpan.setAttribute(SemanticAttributes.FAAS_TRIGGER, 'test faas trigger'); - parentOtelSpan.end(); - - expect(spanToJSON(transaction).op).toBe('test faas trigger'); - expect(spanToJSON(transaction).description).toBe('test operation'); - }); - }); - }); - - describe('skip sentry requests', () => { - it('does not finish transaction for Sentry request', async () => { - const otelSpan = provider.getTracer('default').startSpan('POST to sentry', { - attributes: { - [SemanticAttributes.HTTP_METHOD]: 'POST', - [SemanticAttributes.HTTP_URL]: `${SENTRY_DSN}/sub/route`, - }, - }) as OtelSpan; - - const sentrySpanTransaction = getSpanForOtelSpan(otelSpan) as Transaction | undefined; - expect(sentrySpanTransaction).toBeDefined(); - - otelSpan.end(); - - expect(spanToJSON(sentrySpanTransaction!).timestamp).toBeUndefined(); - - // Ensure it is still removed from map! - expect(getSpanForOtelSpan(otelSpan)).toBeUndefined(); - }); - - it('finishes transaction for non-Sentry request', async () => { - const otelSpan = provider.getTracer('default').startSpan('POST to sentry', { - attributes: { - [SemanticAttributes.HTTP_METHOD]: 'POST', - [SemanticAttributes.HTTP_URL]: 'https://other.sentry.io/sub/route', - }, - }) as OtelSpan; - - const sentrySpanTransaction = getSpanForOtelSpan(otelSpan) as Transaction | undefined; - expect(sentrySpanTransaction).toBeDefined(); - - otelSpan.end(); - - expect(spanToJSON(sentrySpanTransaction!).timestamp).toBeDefined(); - }); - - it('does not finish spans for Sentry request', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parent => { - tracer.startActiveSpan( - 'SELECT * FROM users;', - { - attributes: { - [SemanticAttributes.HTTP_METHOD]: 'POST', - [SemanticAttributes.HTTP_URL]: `${SENTRY_DSN}/sub/route`, - }, - }, - child => { - const childOtelSpan = child as OtelSpan; - - const sentrySpan = getSpanForOtelSpan(childOtelSpan); - expect(sentrySpan).toBeDefined(); - - childOtelSpan.end(); - parent.end(); - - expect(spanToJSON(sentrySpan!).timestamp).toBeUndefined(); - - // Ensure it is still removed from map! - expect(getSpanForOtelSpan(childOtelSpan)).toBeUndefined(); - }, - ); - }); - }); - - it('handles child spans of Sentry requests normally', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parent => { - tracer.startActiveSpan( - 'SELECT * FROM users;', - { - attributes: { - [SemanticAttributes.HTTP_METHOD]: 'POST', - [SemanticAttributes.HTTP_URL]: `${SENTRY_DSN}/sub/route`, - }, - }, - child => { - const grandchild = tracer.startSpan('child 1'); - - const sentrySpan = getSpanForOtelSpan(child); - expect(sentrySpan).toBeDefined(); - - const sentryGrandchildSpan = getSpanForOtelSpan(grandchild); - expect(sentryGrandchildSpan).toBeDefined(); - - grandchild.end(); - child.end(); - parent.end(); - - expect(spanToJSON(sentryGrandchildSpan!).timestamp).toBeDefined(); - expect(spanToJSON(sentrySpan!).timestamp).toBeUndefined(); - }, - ); - }); - }); - }); - - it('associates an error to a transaction', async () => { - let sentryEvent: any; - let otelSpan: any; - - // Clear provider & setup a new one - // As we need a custom client - await provider.forceFlush(); - await provider.shutdown(); - - client = new NodeClient({ - ...DEFAULT_NODE_CLIENT_OPTIONS, - beforeSend: event => { - sentryEvent = event; - return null; - }, - }); - setCurrentClient(client); - client.init(); - - // Need to register the spanprocessor again - spanProcessor = new SentrySpanProcessor(); - provider = new NodeTracerProvider({ - resource: new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: 'test-service', - }), - }); - provider.addSpanProcessor(spanProcessor); - provider.register(); - - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('SELECT * FROM users;', child => { - captureException(new Error('oh nooooo!')); - otelSpan = child as OtelSpan; - child.end(); - }); - - parentOtelSpan.end(); - }); - - expect(sentryEvent).toBeDefined(); - expect(sentryEvent.exception).toBeDefined(); - expect(sentryEvent.contexts.trace).toEqual({ - parent_span_id: otelSpan.parentSpanId, - span_id: otelSpan.spanContext().spanId, - trace_id: otelSpan.spanContext().traceId, - }); - }); - - it('generates Sentry errors from opentelemetry span exception events', () => { - let sentryEvent: any; - let otelSpan: any; - - client = new NodeClient({ - ...DEFAULT_NODE_CLIENT_OPTIONS, - beforeSend: event => { - sentryEvent = event; - return null; - }, - }); - setCurrentClient(client); - client.init(); - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('SELECT * FROM users;', child => { - child.recordException(new Error('this is an otel error!')); - otelSpan = child as OtelSpan; - child.end(); - }); - - parentOtelSpan.end(); - }); - - expect(sentryEvent).toBeDefined(); - expect(sentryEvent.exception).toBeDefined(); - expect(sentryEvent.exception.values[0]).toEqual({ - mechanism: expect.any(Object), - type: 'Error', - value: 'this is an otel error!', - }); - expect(sentryEvent.contexts.trace).toEqual({ - parent_span_id: otelSpan.parentSpanId, - span_id: otelSpan.spanContext().spanId, - trace_id: otelSpan.spanContext().traceId, - }); - }); -}); - -// OTEL expects a custom date format -const NANOSECOND_DIGITS = 9; -const SECOND_TO_NANOSECONDS = Math.pow(10, NANOSECOND_DIGITS); - -function otelNumberToHrtime(epochMillis: number): OpenTelemetry.HrTime { - const epochSeconds = epochMillis / 1000; - // Decimals only. - const seconds = Math.trunc(epochSeconds); - // Round sub-nanosecond accuracy to nanosecond. - const nanos = Number((epochSeconds - seconds).toFixed(NANOSECOND_DIGITS)) * SECOND_TO_NANOSECONDS; - return [seconds, nanos]; -} diff --git a/packages/opentelemetry-node/test/utils/captureExceptionForTimedEvent.test.ts b/packages/opentelemetry-node/test/utils/captureExceptionForTimedEvent.test.ts deleted file mode 100644 index 4d0c39b3a8b9..000000000000 --- a/packages/opentelemetry-node/test/utils/captureExceptionForTimedEvent.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -import type { Span as OtelSpan, TimedEvent } from '@opentelemetry/sdk-trace-base'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import type { Hub } from '@sentry/types'; - -import { maybeCaptureExceptionForTimedEvent } from '../../src/utils/captureExceptionForTimedEvent'; - -describe('maybeCaptureExceptionForTimedEvent', () => { - it('ignores non-exception events', async () => { - const event: TimedEvent = { - time: [12345, 0], - name: 'test event', - }; - - const captureException = jest.fn(); - const hub = { - captureException, - } as unknown as Hub; - - maybeCaptureExceptionForTimedEvent(hub, event); - - expect(captureException).not.toHaveBeenCalled(); - }); - - it('ignores exception events without EXCEPTION_MESSAGE', async () => { - const event: TimedEvent = { - time: [12345, 0], - name: 'exception', - }; - - const captureException = jest.fn(); - const hub = { - captureException, - } as unknown as Hub; - - maybeCaptureExceptionForTimedEvent(hub, event); - - expect(captureException).not.toHaveBeenCalled(); - }); - - it('captures exception from event with EXCEPTION_MESSAGE', async () => { - const event: TimedEvent = { - time: [12345, 0], - name: 'exception', - attributes: { - [SemanticAttributes.EXCEPTION_MESSAGE]: 'test-message', - }, - }; - - const captureException = jest.fn(); - const hub = { - captureException, - } as unknown as Hub; - - maybeCaptureExceptionForTimedEvent(hub, event); - - expect(captureException).toHaveBeenCalledTimes(1); - expect(captureException).toHaveBeenCalledWith(expect.objectContaining({ message: 'test-message' }), { - captureContext: undefined, - }); - expect(captureException).toHaveBeenCalledWith(expect.any(Error), { - captureContext: undefined, - }); - }); - - it('captures stack and type, if available', async () => { - const event: TimedEvent = { - time: [12345, 0], - name: 'exception', - attributes: { - [SemanticAttributes.EXCEPTION_MESSAGE]: 'test-message', - [SemanticAttributes.EXCEPTION_STACKTRACE]: 'test-stack', - [SemanticAttributes.EXCEPTION_TYPE]: 'test-type', - }, - }; - - const captureException = jest.fn(); - const hub = { - captureException, - } as unknown as Hub; - - maybeCaptureExceptionForTimedEvent(hub, event); - - expect(captureException).toHaveBeenCalledTimes(1); - expect(captureException).toHaveBeenCalledWith( - expect.objectContaining({ message: 'test-message', name: 'test-type', stack: 'test-stack' }), - { - captureContext: undefined, - }, - ); - expect(captureException).toHaveBeenCalledWith(expect.any(Error), { - captureContext: undefined, - }); - }); - - it('captures span context, if available', async () => { - const event: TimedEvent = { - time: [12345, 0], - name: 'exception', - attributes: { - [SemanticAttributes.EXCEPTION_MESSAGE]: 'test-message', - }, - }; - - const span = { - parentSpanId: 'test-parent-span-id', - attributes: { - 'test-attr1': 'test-value1', - }, - resource: { - attributes: { - 'test-attr2': 'test-value2', - }, - }, - spanContext: () => { - return { spanId: 'test-span-id', traceId: 'test-trace-id' }; - }, - } as unknown as OtelSpan; - - const captureException = jest.fn(); - const hub = { - captureException, - } as unknown as Hub; - - maybeCaptureExceptionForTimedEvent(hub, event, span); - - expect(captureException).toHaveBeenCalledTimes(1); - expect(captureException).toHaveBeenCalledWith(expect.objectContaining({ message: 'test-message' }), { - captureContext: { - contexts: { - otel: { - attributes: { - 'test-attr1': 'test-value1', - }, - resource: { - 'test-attr2': 'test-value2', - }, - }, - trace: { - trace_id: 'test-trace-id', - span_id: 'test-span-id', - parent_span_id: 'test-parent-span-id', - }, - }, - }, - }); - }); -}); diff --git a/packages/opentelemetry-node/test/utils/parseOtelSpanDescription.test.ts b/packages/opentelemetry-node/test/utils/parseOtelSpanDescription.test.ts deleted file mode 100644 index b2d1b3654500..000000000000 --- a/packages/opentelemetry-node/test/utils/parseOtelSpanDescription.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { SpanKind } from '@opentelemetry/api'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; - -import { getSanitizedUrl } from '../../src/utils/parseOtelSpanDescription'; - -describe('getSanitizedUrl', () => { - it.each([ - [ - 'works without attributes', - {}, - SpanKind.CLIENT, - { - urlPath: undefined, - url: undefined, - fragment: undefined, - query: undefined, - }, - ], - [ - 'uses url without query for client request', - { - [SemanticAttributes.HTTP_URL]: 'http://example.com/?what=true', - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_TARGET]: '/?what=true', - [SemanticAttributes.HTTP_HOST]: 'example.com:80', - [SemanticAttributes.HTTP_STATUS_CODE]: 200, - }, - SpanKind.CLIENT, - { - urlPath: 'http://example.com/', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - }, - ], - [ - 'uses url without hash for client request', - { - [SemanticAttributes.HTTP_URL]: 'http://example.com/sub#hash', - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_TARGET]: '/sub#hash', - [SemanticAttributes.HTTP_HOST]: 'example.com:80', - [SemanticAttributes.HTTP_STATUS_CODE]: 200, - }, - SpanKind.CLIENT, - { - urlPath: 'http://example.com/sub', - url: 'http://example.com/sub', - fragment: '#hash', - query: undefined, - }, - ], - [ - 'uses route if available for client request', - { - [SemanticAttributes.HTTP_URL]: 'http://example.com/?what=true', - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_TARGET]: '/?what=true', - [SemanticAttributes.HTTP_ROUTE]: '/my-route', - [SemanticAttributes.HTTP_HOST]: 'example.com:80', - [SemanticAttributes.HTTP_STATUS_CODE]: 200, - }, - SpanKind.CLIENT, - { - urlPath: '/my-route', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - }, - ], - [ - 'falls back to target for client request if url not available', - { - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_TARGET]: '/?what=true', - [SemanticAttributes.HTTP_HOST]: 'example.com:80', - [SemanticAttributes.HTTP_STATUS_CODE]: 200, - }, - SpanKind.CLIENT, - { - urlPath: '/', - url: undefined, - fragment: undefined, - query: undefined, - }, - ], - [ - 'uses target without query for server request', - { - [SemanticAttributes.HTTP_URL]: 'http://example.com/?what=true', - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_TARGET]: '/?what=true', - [SemanticAttributes.HTTP_HOST]: 'example.com:80', - [SemanticAttributes.HTTP_STATUS_CODE]: 200, - }, - SpanKind.SERVER, - { - urlPath: '/', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - }, - ], - [ - 'uses target without hash for server request', - { - [SemanticAttributes.HTTP_URL]: 'http://example.com/?what=true', - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_TARGET]: '/sub#hash', - [SemanticAttributes.HTTP_HOST]: 'example.com:80', - [SemanticAttributes.HTTP_STATUS_CODE]: 200, - }, - SpanKind.SERVER, - { - urlPath: '/sub', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - }, - ], - [ - 'uses route for server request if available', - { - [SemanticAttributes.HTTP_URL]: 'http://example.com/?what=true', - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_TARGET]: '/?what=true', - [SemanticAttributes.HTTP_ROUTE]: '/my-route', - [SemanticAttributes.HTTP_HOST]: 'example.com:80', - [SemanticAttributes.HTTP_STATUS_CODE]: 200, - }, - SpanKind.SERVER, - { - urlPath: '/my-route', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - }, - ], - ])('%s', (_, attributes, kind, expected) => { - const actual = getSanitizedUrl(attributes, kind); - - expect(actual).toEqual(expected); - }); -}); diff --git a/packages/opentelemetry-node/tsconfig.json b/packages/opentelemetry-node/tsconfig.json deleted file mode 100644 index bf45a09f2d71..000000000000 --- a/packages/opentelemetry-node/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.json", - - "include": ["src/**/*"], - - "compilerOptions": { - // package-specific options - } -} diff --git a/packages/opentelemetry-node/tsconfig.test.json b/packages/opentelemetry-node/tsconfig.test.json deleted file mode 100644 index 87f6afa06b86..000000000000 --- a/packages/opentelemetry-node/tsconfig.test.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "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/opentelemetry-node/tsconfig.types.json b/packages/opentelemetry-node/tsconfig.types.json deleted file mode 100644 index 65455f66bd75..000000000000 --- a/packages/opentelemetry-node/tsconfig.types.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - - "compilerOptions": { - "declaration": true, - "declarationMap": true, - "emitDeclarationOnly": true, - "outDir": "build/types" - } -} diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index 1bd423116e8f..25d2fe0b54ed 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -227,12 +227,6 @@ export interface Client { */ 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; - /** * Register a callback when a Feedback event has been prepared. * This should be used to mutate the event. The options argument can hint @@ -310,13 +304,6 @@ export interface Client { */ 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; - /** * Fire a hook event for after preparing a feedback event. Events to be given * a feedback event as the second argument, and an optional options object as