diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json index 8a1634725184..b02788c4761f 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json +++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/package.json @@ -1,5 +1,5 @@ { - "name": "node-otel-sdk-trace", + "name": "node-otel-sdk-node", "version": "1.0.0", "private": true, "scripts": { diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/start-event-proxy.mjs index 8c74fa842a1b..b97bfc4664dd 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/start-event-proxy.mjs +++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/start-event-proxy.mjs @@ -2,5 +2,5 @@ import { startEventProxyServer } from '@sentry-internal/test-utils'; startEventProxyServer({ port: 3031, - proxyServerName: 'node-otel-sdk-trace', + proxyServerName: 'node-otel-sdk-node', }); diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/start-otel-proxy.mjs b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/start-otel-proxy.mjs index 1cf9ef3e2c27..c24241310fbb 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/start-otel-proxy.mjs +++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/start-otel-proxy.mjs @@ -2,5 +2,5 @@ import { startProxyServer } from '@sentry-internal/test-utils'; startProxyServer({ port: 3032, - proxyServerName: 'node-otel-sdk-trace-otel', + proxyServerName: 'node-otel-sdk-node-otel', }); diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/errors.test.ts index 9cb97a051476..7dbb66a4119d 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/errors.test.ts @@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test'; import { waitForError } from '@sentry-internal/test-utils'; test('Sends correct error event', async ({ baseURL }) => { - const errorEventPromise = waitForError('node-otel-sdk-trace', event => { + const errorEventPromise = waitForError('node-otel-sdk-node', event => { return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; }); diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts index f7fee0559a97..ebf500ffb09c 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts @@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test'; import { waitForPlainRequest, waitForTransaction } from '@sentry-internal/test-utils'; test('Sends an API route transaction', async ({ baseURL }) => { - const pageloadTransactionEventPromise = waitForTransaction('node-otel-sdk-trace', transactionEvent => { + const pageloadTransactionEventPromise = waitForTransaction('node-otel-sdk-node', transactionEvent => { return ( transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent?.transaction === 'GET /test-transaction' @@ -10,7 +10,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { }); // Ensure we also send data to the OTLP endpoint - const otelPromise = waitForPlainRequest('node-otel-sdk-trace-otel', data => { + const otelPromise = waitForPlainRequest('node-otel-sdk-node-otel', data => { const json = JSON.parse(data) as any; return json.resourceSpans.length > 0; @@ -129,7 +129,7 @@ test('Sends an API route transaction', async ({ baseURL }) => { }); test('Sends an API route transaction for an errored route', async ({ baseURL }) => { - const transactionEventPromise = waitForTransaction('node-otel-sdk-trace', transactionEvent => { + const transactionEventPromise = waitForTransaction('node-otel-sdk-node', transactionEvent => { return ( transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.transaction === 'GET /test-exception/:id' && diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/.gitignore b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/.gitignore new file mode 100644 index 000000000000..1521c8b7652b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/.gitignore @@ -0,0 +1 @@ +dist diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/.npmrc b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json new file mode 100644 index 000000000000..1683d4166af9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/package.json @@ -0,0 +1,34 @@ +{ + "name": "node-otel-without-tracing", + "version": "1.0.0", + "private": true, + "scripts": { + "build": "tsc", + "start": "node dist/app.js", + "test": "playwright test", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test:build": "pnpm install && pnpm build", + "test:assert": "pnpm test" + }, + "dependencies": { + "@opentelemetry/sdk-trace-node": "1.25.1", + "@opentelemetry/exporter-trace-otlp-http": "0.52.1", + "@opentelemetry/instrumentation-undici": "0.4.0", + "@opentelemetry/instrumentation": "0.52.1", + "@sentry/core": "latest || *", + "@sentry/node": "latest || *", + "@sentry/opentelemetry": "latest || *", + "@sentry/types": "latest || *", + "@types/express": "4.17.17", + "@types/node": "18.15.1", + "express": "4.19.2", + "typescript": "4.9.5" + }, + "devDependencies": { + "@playwright/test": "^1.44.1", + "@sentry-internal/test-utils": "link:../../../test-utils" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/playwright.config.mjs new file mode 100644 index 000000000000..888e61cfb2dc --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/playwright.config.mjs @@ -0,0 +1,34 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig( + { + startCommand: `pnpm start`, + }, + { + webServer: [ + { + command: `node ./start-event-proxy.mjs`, + port: 3031, + stdout: 'pipe', + stderr: 'pipe', + }, + { + command: `node ./start-otel-proxy.mjs`, + port: 3032, + stdout: 'pipe', + stderr: 'pipe', + }, + { + command: 'pnpm start', + port: 3030, + stdout: 'pipe', + stderr: 'pipe', + env: { + PORT: 3030, + }, + }, + ], + }, +); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/app.ts b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/app.ts new file mode 100644 index 000000000000..c3fdfb9134a5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/app.ts @@ -0,0 +1,55 @@ +import './instrument'; + +// Other imports below +import * as Sentry from '@sentry/node'; +import express from 'express'; + +const app = express(); +const port = 3030; + +app.get('/test-success', function (req, res) { + res.send({ version: 'v1' }); +}); + +app.get('/test-param/:param', function (req, res) { + res.send({ paramWas: req.params.param }); +}); + +app.get('/test-transaction', function (req, res) { + Sentry.withActiveSpan(null, async () => { + Sentry.startSpan({ name: 'test-transaction', op: 'e2e-test' }, () => { + Sentry.startSpan({ name: 'test-span' }, () => undefined); + }); + + await fetch('http://localhost:3030/test-success'); + + await Sentry.flush(); + + res.send({}); + }); +}); + +app.get('/test-error', async function (req, res) { + const exceptionId = Sentry.captureException(new Error('This is an error')); + + await Sentry.flush(2000); + + res.send({ exceptionId }); +}); + +app.get('/test-exception/:id', function (req, _res) { + throw new Error(`This is an exception with id ${req.params.id}`); +}); + +Sentry.setupExpressErrorHandler(app); + +app.use(function onError(err: unknown, req: any, res: any, next: any) { + // The error id is attached to `res.sentry` to be returned + // and optionally displayed to the user for support. + res.statusCode = 500; + res.end(res.sentry + '\n'); +}); + +app.listen(port, () => { + console.log(`Example app listening on port ${port}`); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts new file mode 100644 index 000000000000..8100d27af965 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/src/instrument.ts @@ -0,0 +1,41 @@ +const { NodeTracerProvider, BatchSpanProcessor } = require('@opentelemetry/sdk-trace-node'); +const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http'); +const Sentry = require('@sentry/node'); +const { SentrySpanProcessor, SentryPropagator } = require('@sentry/opentelemetry'); +const { UndiciInstrumentation } = require('@opentelemetry/instrumentation-undici'); +const { registerInstrumentations } = require('@opentelemetry/instrumentation'); + +const sentryClient = Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: + process.env.E2E_TEST_DSN || + 'https://3b6c388182fb435097f41d181be2b2ba@o4504321058471936.ingest.sentry.io/4504321066008576', + includeLocalVariables: true, + debug: !!process.env.DEBUG, + tunnel: `http://localhost:3031/`, // proxy server + // Tracing is completely disabled + + // Custom OTEL setup + skipOpenTelemetrySetup: true, +}); + +// Create and configure NodeTracerProvider +const provider = new NodeTracerProvider({}); + +provider.addSpanProcessor( + new BatchSpanProcessor( + new OTLPTraceExporter({ + url: 'http://localhost:3032/', + }), + ), +); + +// Initialize the provider +provider.register({ + propagator: new SentryPropagator(), + contextManager: new Sentry.SentryContextManager(), +}); + +registerInstrumentations({ + instrumentations: [new UndiciInstrumentation()], +}); diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/start-event-proxy.mjs new file mode 100644 index 000000000000..62073e9a9b6e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'node-otel-without-tracing', +}); diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/start-otel-proxy.mjs b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/start-otel-proxy.mjs new file mode 100644 index 000000000000..1e182efd3873 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/start-otel-proxy.mjs @@ -0,0 +1,6 @@ +import { startProxyServer } from '@sentry-internal/test-utils'; + +startProxyServer({ + port: 3032, + proxyServerName: 'node-otel-without-tracing-otel', +}); diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/tests/errors.test.ts new file mode 100644 index 000000000000..28e63f02090c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/tests/errors.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/test-utils'; + +test('Sends correct error event', async ({ baseURL }) => { + const errorEventPromise = waitForError('node-otel-without-tracing', event => { + return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; + }); + + await fetch(`${baseURL}/test-exception/123`); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an exception with id 123'); + + expect(errorEvent.request).toEqual({ + method: 'GET', + cookies: {}, + headers: expect.any(Object), + url: 'http://localhost:3030/test-exception/123', + }); + + // This is unparametrized here because we do not have the express instrumentation + expect(errorEvent.transaction).toEqual('GET /test-exception/123'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/tests/transactions.test.ts new file mode 100644 index 000000000000..abc55344327c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/tests/transactions.test.ts @@ -0,0 +1,173 @@ +import { expect, test } from '@playwright/test'; +import { waitForPlainRequest, waitForTransaction } from '@sentry-internal/test-utils'; + +test('Sends an API route transaction to OTLP', async ({ baseURL }) => { + waitForTransaction('node-otel-without-tracing', transactionEvent => { + throw new Error('THIS SHOULD NEVER HAPPEN!'); + }); + + // Ensure we send data to the OTLP endpoint + const otelPromise = waitForPlainRequest('node-otel-without-tracing-otel', data => { + const json = JSON.parse(data) as any; + + const scopeSpans = json.resourceSpans?.[0]?.scopeSpans; + + const httpScope = scopeSpans?.find(scopeSpan => scopeSpan.scope.name === '@opentelemetry/instrumentation-http'); + + return ( + httpScope && + httpScope.spans.some(span => + span.attributes.some(attr => attr.key === 'http.target' && attr.value?.stringValue === '/test-transaction'), + ) + ); + }); + + await fetch(`${baseURL}/test-transaction`); + + const otelData = await otelPromise; + + expect(otelData).toBeDefined(); + + const json = JSON.parse(otelData); + expect(json.resourceSpans.length).toBe(1); + + const scopeSpans = json.resourceSpans?.[0]?.scopeSpans; + expect(scopeSpans).toBeDefined(); + + // Http server span & undici client spans are emitted + // But our default node-fetch spans are not emitted + expect(scopeSpans.length).toEqual(2); + + const httpScopes = scopeSpans?.filter(scopeSpan => scopeSpan.scope.name === '@opentelemetry/instrumentation-http'); + const undiciScopes = scopeSpans?.filter( + scopeSpan => scopeSpan.scope.name === '@opentelemetry/instrumentation-undici', + ); + + expect(httpScopes.length).toBe(1); + + // Undici spans are emitted correctly + expect(undiciScopes.length).toBe(1); + expect(undiciScopes[0].spans.length).toBe(1); + + // There may be another span from another request, we can ignore that + const httpSpans = httpScopes[0].spans.filter(span => + span.attributes.some(attr => attr.key === 'http.target' && attr.value?.stringValue === '/test-transaction'), + ); + + expect(httpSpans).toEqual([ + { + traceId: expect.any(String), + spanId: expect.any(String), + name: 'GET', + kind: 2, + startTimeUnixNano: expect.any(String), + endTimeUnixNano: expect.any(String), + attributes: [ + { + key: 'http.url', + value: { + stringValue: 'http://localhost:3030/test-transaction', + }, + }, + { + key: 'http.host', + value: { + stringValue: 'localhost:3030', + }, + }, + { + key: 'net.host.name', + value: { + stringValue: 'localhost', + }, + }, + { + key: 'http.method', + value: { + stringValue: 'GET', + }, + }, + { + key: 'http.scheme', + value: { + stringValue: 'http', + }, + }, + { + key: 'http.target', + value: { + stringValue: '/test-transaction', + }, + }, + { + key: 'http.user_agent', + value: { + stringValue: 'node', + }, + }, + { + key: 'http.flavor', + value: { + stringValue: '1.1', + }, + }, + { + key: 'net.transport', + value: { + stringValue: 'ip_tcp', + }, + }, + { + key: 'sentry.origin', + value: { + stringValue: 'auto.http.otel.http', + }, + }, + { + key: 'net.host.ip', + value: { + stringValue: expect.any(String), + }, + }, + { + key: 'net.host.port', + value: { + intValue: 3030, + }, + }, + { + key: 'net.peer.ip', + value: { + stringValue: expect.any(String), + }, + }, + { + key: 'net.peer.port', + value: { + intValue: expect.any(Number), + }, + }, + { + key: 'http.status_code', + value: { + intValue: 200, + }, + }, + { + key: 'http.status_text', + value: { + stringValue: 'OK', + }, + }, + ], + droppedAttributesCount: 0, + events: [], + droppedEventsCount: 0, + status: { + code: 0, + }, + links: [], + droppedLinksCount: 0, + }, + ]); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/tsconfig.json new file mode 100644 index 000000000000..d14f5822baf2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-otel-without-tracing/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "types": ["node"], + "esModuleInterop": true, + "lib": ["es2018", "dom"], + "strict": true, + "outDir": "dist" + }, + "include": ["src/**/*.ts"] +} diff --git a/dev-packages/node-integration-tests/jest.setup.js b/dev-packages/node-integration-tests/jest.setup.js index 7c1837cab523..b0c26e5b05f2 100644 --- a/dev-packages/node-integration-tests/jest.setup.js +++ b/dev-packages/node-integration-tests/jest.setup.js @@ -1,2 +1,8 @@ +const { cleanupChildProcesses } = require('./utils/runner'); + // Increases test timeout from 5s to 45s jest.setTimeout(45000); + +afterEach(() => { + cleanupChildProcesses(); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/fetch-noSampleRate/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/requests/fetch-no-tracing/scenario.ts similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/requests/fetch-noSampleRate/scenario.ts rename to dev-packages/node-integration-tests/suites/tracing/requests/fetch-no-tracing/scenario.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/fetch-noSampleRate/test.ts b/dev-packages/node-integration-tests/suites/tracing/requests/fetch-no-tracing/test.ts similarity index 82% rename from dev-packages/node-integration-tests/suites/tracing/requests/fetch-noSampleRate/test.ts rename to dev-packages/node-integration-tests/suites/tracing/requests/fetch-no-tracing/test.ts index f69f4f54c56d..f9ad7f92d3f1 100644 --- a/dev-packages/node-integration-tests/suites/tracing/requests/fetch-noSampleRate/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/requests/fetch-no-tracing/test.ts @@ -3,31 +3,27 @@ import { createRunner } from '../../../../utils/runner'; import { createTestServer } from '../../../../utils/server'; conditionalTest({ min: 18 })('outgoing fetch', () => { - test('outgoing fetch requests are correctly instrumented without tracesSampleRate', done => { - expect.assertions(15); + test('outgoing fetch requests are correctly instrumented with tracing disabled', done => { + expect.assertions(11); createTestServer(done) .get('/api/v0', headers => { expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})$/)); expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000'); expect(headers['baggage']).toEqual(expect.any(String)); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v1', headers => { expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})$/)); expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000'); expect(headers['baggage']).toEqual(expect.any(String)); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v2', headers => { expect(headers['baggage']).toBeUndefined(); expect(headers['sentry-trace']).toBeUndefined(); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v3', headers => { expect(headers['baggage']).toBeUndefined(); expect(headers['sentry-trace']).toBeUndefined(); - expect(headers['__requestUrl']).toBeUndefined(); }) .start() .then(SERVER_URL => { diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/fetch-sampled/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/requests/fetch-sampled/scenario.ts deleted file mode 100644 index 4d47e16fd42f..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/requests/fetch-sampled/scenario.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { loggingTransport } from '@sentry-internal/node-integration-tests'; -import * as Sentry from '@sentry/node'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - release: '1.0', - tracesSampleRate: 1.0, - tracePropagationTargets: [/\/v0/, 'v1'], - integrations: [], - transport: loggingTransport, -}); - -async function run(): Promise { - await Sentry.startSpan({ name: 'test_span' }, async () => { - // Since fetch is lazy loaded, we need to wait a bit until it's fully instrumented - await new Promise(resolve => setTimeout(resolve, 100)); - await fetch(`${process.env.SERVER_URL}/api/v0`).then(res => res.text()); - await fetch(`${process.env.SERVER_URL}/api/v1`).then(res => res.text()); - await fetch(`${process.env.SERVER_URL}/api/v2`).then(res => res.text()); - await fetch(`${process.env.SERVER_URL}/api/v3`).then(res => res.text()); - }); -} - -// eslint-disable-next-line @typescript-eslint/no-floating-promises -run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/fetch-sampled/test.ts b/dev-packages/node-integration-tests/suites/tracing/requests/fetch-sampled/test.ts deleted file mode 100644 index 40d05d2131eb..000000000000 --- a/dev-packages/node-integration-tests/suites/tracing/requests/fetch-sampled/test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { conditionalTest } from '../../../../utils'; -import { createRunner } from '../../../../utils/runner'; -import { createTestServer } from '../../../../utils/server'; - -conditionalTest({ min: 18 })('outgoing fetch', () => { - test('outgoing sampled fetch requests are correctly instrumented', done => { - expect.assertions(11); - - createTestServer(done) - .get('/api/v0', headers => { - expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})-1$/)); - expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000-1'); - expect(headers['baggage']).toEqual(expect.any(String)); - }) - .get('/api/v1', headers => { - expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})-1$/)); - expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000-1'); - expect(headers['baggage']).toEqual(expect.any(String)); - }) - .get('/api/v2', headers => { - expect(headers['baggage']).toBeUndefined(); - expect(headers['sentry-trace']).toBeUndefined(); - }) - .get('/api/v3', headers => { - expect(headers['baggage']).toBeUndefined(); - expect(headers['sentry-trace']).toBeUndefined(); - }) - .start() - .then(SERVER_URL => { - createRunner(__dirname, 'scenario.ts') - .withEnv({ SERVER_URL }) - .expect({ - transaction: { - // we're not too concerned with the actual transaction here since this is tested elsewhere - }, - }) - .start(done); - }); - }); -}); diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/http-noSampleRate/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing/scenario.ts similarity index 100% rename from dev-packages/node-integration-tests/suites/tracing/requests/http-noSampleRate/scenario.ts rename to dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing/scenario.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/http-noSampleRate/test.ts b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing/test.ts similarity index 81% rename from dev-packages/node-integration-tests/suites/tracing/requests/http-noSampleRate/test.ts rename to dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing/test.ts index b6766442683e..308e0c6676e2 100644 --- a/dev-packages/node-integration-tests/suites/tracing/requests/http-noSampleRate/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing/test.ts @@ -1,31 +1,27 @@ import { createRunner } from '../../../../utils/runner'; import { createTestServer } from '../../../../utils/server'; -test('outgoing http requests are correctly instrumented without tracesSampleRate', done => { - expect.assertions(15); +test('outgoing http requests are correctly instrumented with tracing disabled', done => { + expect.assertions(11); createTestServer(done) .get('/api/v0', headers => { expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})$/)); expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000'); expect(headers['baggage']).toEqual(expect.any(String)); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v1', headers => { expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})$/)); expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000'); expect(headers['baggage']).toEqual(expect.any(String)); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v2', headers => { expect(headers['baggage']).toBeUndefined(); expect(headers['sentry-trace']).toBeUndefined(); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v3', headers => { expect(headers['baggage']).toBeUndefined(); expect(headers['sentry-trace']).toBeUndefined(); - expect(headers['__requestUrl']).toBeUndefined(); }) .start() .then(SERVER_URL => { diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/http-sampled-no-active-span/test.ts b/dev-packages/node-integration-tests/suites/tracing/requests/http-sampled-no-active-span/test.ts index 9f18f050b929..83d8774dbd46 100644 --- a/dev-packages/node-integration-tests/suites/tracing/requests/http-sampled-no-active-span/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/requests/http-sampled-no-active-span/test.ts @@ -2,30 +2,26 @@ import { createRunner } from '../../../../utils/runner'; import { createTestServer } from '../../../../utils/server'; test('outgoing sampled http requests without active span are correctly instrumented', done => { - expect.assertions(15); + expect.assertions(11); createTestServer(done) .get('/api/v0', headers => { expect(headers['baggage']).toEqual(expect.any(String)); expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})$/)); expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000'); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v1', headers => { expect(headers['baggage']).toEqual(expect.any(String)); expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})$/)); expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000'); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v2', headers => { expect(headers['baggage']).toBeUndefined(); expect(headers['sentry-trace']).toBeUndefined(); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v3', headers => { expect(headers['baggage']).toBeUndefined(); expect(headers['sentry-trace']).toBeUndefined(); - expect(headers['__requestUrl']).toBeUndefined(); }) .start() .then(SERVER_URL => { diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/http-sampled/test.ts b/dev-packages/node-integration-tests/suites/tracing/requests/http-sampled/test.ts index f3ad8bc5728e..fd939bc4458c 100644 --- a/dev-packages/node-integration-tests/suites/tracing/requests/http-sampled/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/requests/http-sampled/test.ts @@ -2,30 +2,26 @@ import { createRunner } from '../../../../utils/runner'; import { createTestServer } from '../../../../utils/server'; test('outgoing sampled http requests are correctly instrumented', done => { - expect.assertions(15); + expect.assertions(11); createTestServer(done) .get('/api/v0', headers => { expect(headers['baggage']).toEqual(expect.any(String)); expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})-1$/)); expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000-1'); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v1', headers => { expect(headers['baggage']).toEqual(expect.any(String)); expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})-1$/)); expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000-1'); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v2', headers => { expect(headers['baggage']).toBeUndefined(); expect(headers['sentry-trace']).toBeUndefined(); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v3', headers => { expect(headers['baggage']).toBeUndefined(); expect(headers['sentry-trace']).toBeUndefined(); - expect(headers['__requestUrl']).toBeUndefined(); }) .start() .then(SERVER_URL => { diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/http-unsampled/test.ts b/dev-packages/node-integration-tests/suites/tracing/requests/http-unsampled/test.ts index be4a2f542875..ed5d30631f31 100644 --- a/dev-packages/node-integration-tests/suites/tracing/requests/http-unsampled/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/requests/http-unsampled/test.ts @@ -2,30 +2,26 @@ import { createRunner } from '../../../../utils/runner'; import { createTestServer } from '../../../../utils/server'; test('outgoing http requests are correctly instrumented when not sampled', done => { - expect.assertions(15); + expect.assertions(11); createTestServer(done) .get('/api/v0', headers => { expect(headers['baggage']).toEqual(expect.any(String)); expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})-0$/)); expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000-0'); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v1', headers => { expect(headers['baggage']).toEqual(expect.any(String)); expect(headers['sentry-trace']).toEqual(expect.stringMatching(/^([a-f0-9]{32})-([a-f0-9]{16})-0$/)); expect(headers['sentry-trace']).not.toEqual('00000000000000000000000000000000-0000000000000000-0'); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v2', headers => { expect(headers['baggage']).toBeUndefined(); expect(headers['sentry-trace']).toBeUndefined(); - expect(headers['__requestUrl']).toBeUndefined(); }) .get('/api/v3', headers => { expect(headers['baggage']).toBeUndefined(); expect(headers['sentry-trace']).toBeUndefined(); - expect(headers['__requestUrl']).toBeUndefined(); }) .start() .then(SERVER_URL => { diff --git a/packages/node/src/integrations/node-fetch.ts b/packages/node/src/integrations/node-fetch.ts index 79b5aa10acad..093b314a6138 100644 --- a/packages/node/src/integrations/node-fetch.ts +++ b/packages/node/src/integrations/node-fetch.ts @@ -1,6 +1,12 @@ import type { Span } from '@opentelemetry/api'; -import { addBreadcrumb, defineIntegration } from '@sentry/core'; -import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry'; +import { trace } from '@opentelemetry/api'; +import { context, propagation } from '@opentelemetry/api'; +import { addBreadcrumb, defineIntegration, getCurrentScope, hasTracingEnabled } from '@sentry/core'; +import { + addOpenTelemetryInstrumentation, + generateSpanContextForPropagationContext, + getPropagationContextFromSpan, +} from '@sentry/opentelemetry'; import type { IntegrationFn, SanitizedRequestData } from '@sentry/types'; import { getSanitizedUrlString, logger, parseUrl } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; @@ -63,9 +69,49 @@ const _nativeNodeFetchIntegration = ((options: NodeFetchOptions = {}) => { } return new SentryNodeFetchInstrumentation({ - ignoreRequestHook: (request: { origin?: string }) => { - const url = request.origin; - return _ignoreOutgoingRequests && url && _ignoreOutgoingRequests(url); + ignoreRequestHook: (request: FetchRequest) => { + const url = getAbsoluteUrl(request.origin, request.path); + const tracingDisabled = !hasTracingEnabled(); + const shouldIgnore = _ignoreOutgoingRequests && url && _ignoreOutgoingRequests(url); + + if (shouldIgnore) { + return true; + } + + // If tracing is disabled, we still want to propagate traces + // So we do that manually here, matching what the instrumentation does otherwise + if (tracingDisabled) { + const ctx = context.active(); + const addedHeaders: Record = {}; + + // We generate a virtual span context from the active one, + // Where we attach the URL to the trace state, so the propagator can pick it up + const activeSpan = trace.getSpan(ctx); + const propagationContext = activeSpan + ? getPropagationContextFromSpan(activeSpan) + : getCurrentScope().getPropagationContext(); + + const spanContext = generateSpanContextForPropagationContext(propagationContext); + // We know that in practice we'll _always_ haven a traceState here + spanContext.traceState = spanContext.traceState?.set('sentry.url', url); + const ctxWithUrlTraceState = trace.setSpanContext(ctx, spanContext); + + propagation.inject(ctxWithUrlTraceState, addedHeaders); + + const requestHeaders = request.headers; + if (Array.isArray(requestHeaders)) { + Object.entries(addedHeaders).forEach(headers => requestHeaders.push(...headers)); + } else { + request.headers += Object.entries(addedHeaders) + .map(([k, v]) => `${k}: ${v}\r\n`) + .join(''); + } + + // Prevent starting a span for this request + return true; + } + + return false; }, onRequest: ({ span }: { span: Span }) => { _updateSpan(span); @@ -141,3 +187,18 @@ function getBreadcrumbData(request: FetchRequest): Partial return {}; } } + +// Matching the behavior of the base instrumentation +function getAbsoluteUrl(origin: string, path: string = '/'): string { + const url = `${origin}`; + + if (origin.endsWith('/') && path.startsWith('/')) { + return `${url}${path.slice(1)}`; + } + + if (!origin.endsWith('/') && !path.startsWith('/')) { + return `${url}/${path.slice(1)}`; + } + + return `${url}${path}`; +} diff --git a/packages/opentelemetry/src/index.ts b/packages/opentelemetry/src/index.ts index e262247bce1e..ef57ab0fff3d 100644 --- a/packages/opentelemetry/src/index.ts +++ b/packages/opentelemetry/src/index.ts @@ -22,6 +22,7 @@ export { getDynamicSamplingContextFromSpan } from '@sentry/core'; export { isSentryRequestSpan } from './utils/isSentryRequest'; export { enhanceDscWithOpenTelemetryRootSpanName } from './utils/enhanceDscWithOpenTelemetryRootSpanName'; +export { generateSpanContextForPropagationContext } from './utils/generateSpanContextForPropagationContext'; export { getActiveSpan } from './utils/getActiveSpan'; export { startSpan, startSpanManual, startInactiveSpan, withActiveSpan, continueTrace } from './trace'; @@ -34,7 +35,7 @@ export { setupEventContextTrace } from './setupEventContextTrace'; export { setOpenTelemetryContextAsyncContextStrategy } from './asyncContextStrategy'; export { wrapContextManagerClass } from './contextManager'; -export { SentryPropagator } from './propagator'; +export { SentryPropagator, getPropagationContextFromSpan } from './propagator'; export { SentrySpanProcessor } from './spanProcessor'; export { SentrySampler, diff --git a/packages/opentelemetry/src/propagator.ts b/packages/opentelemetry/src/propagator.ts index 2fa125e86b86..40b8a8139b0d 100644 --- a/packages/opentelemetry/src/propagator.ts +++ b/packages/opentelemetry/src/propagator.ts @@ -1,8 +1,8 @@ -import type { Baggage, Context, Span, SpanContext, TextMapGetter, TextMapSetter } from '@opentelemetry/api'; +import type { Baggage, Context, Span, TextMapGetter, TextMapSetter } from '@opentelemetry/api'; import { INVALID_TRACEID } from '@opentelemetry/api'; import { context } from '@opentelemetry/api'; -import { TraceFlags, propagation, trace } from '@opentelemetry/api'; -import { TraceState, W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core'; +import { propagation, trace } from '@opentelemetry/api'; +import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core'; import { SEMATTRS_HTTP_URL } from '@opentelemetry/semantic-conventions'; import type { continueTrace } from '@sentry/core'; import { hasTracingEnabled } from '@sentry/core'; @@ -20,7 +20,6 @@ import { LRUMap, SENTRY_BAGGAGE_KEY_PREFIX, baggageHeaderToDynamicSamplingContext, - dynamicSamplingContextToSentryBaggageHeader, generateSentryTraceHeader, logger, parseBaggageHeader, @@ -33,11 +32,11 @@ import { SENTRY_TRACE_HEADER, SENTRY_TRACE_STATE_DSC, SENTRY_TRACE_STATE_PARENT_SPAN_ID, - SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, SENTRY_TRACE_STATE_URL, } from './constants'; import { DEBUG_BUILD } from './debug-build'; import { getScopesFromContext, setScopesOnContext } from './utils/contextData'; +import { generateSpanContextForPropagationContext } from './utils/generateSpanContextForPropagationContext'; import { getSamplingDecision } from './utils/getSamplingDecision'; import { setIsSetup } from './utils/setupCheck'; @@ -193,32 +192,6 @@ export class SentryPropagator extends W3CBaggagePropagator { } } -/** Exported for tests. */ -export function makeTraceState({ - parentSpanId, - dsc, - sampled, -}: { - parentSpanId?: string; - dsc?: Partial; - sampled?: boolean; -}): TraceState { - // We store the DSC as OTEL trace state on the span context - const dscString = dsc ? dynamicSamplingContextToSentryBaggageHeader(dsc) : undefined; - - // We _always_ set the parent span ID, even if it is empty - // If we'd set this to 'undefined' we could not know if the trace state was set, but there was no parentSpanId, - // vs the trace state was not set at all (in which case we want to do fallback handling) - // If `''`, it should be considered "no parent" - const traceStateBase = new TraceState().set(SENTRY_TRACE_STATE_PARENT_SPAN_ID, parentSpanId || ''); - - const traceStateWithDsc = dscString ? traceStateBase.set(SENTRY_TRACE_STATE_DSC, dscString) : traceStateBase; - - // We also specifically want to store if this is sampled to be not recording, - // or unsampled (=could be either sampled or not) - return sampled === false ? traceStateWithDsc.set(SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, '1') : traceStateWithDsc; -} - function getInjectionData(context: Context): { dynamicSamplingContext: Partial | undefined; traceId: string | undefined; @@ -281,21 +254,7 @@ function getContextWithRemoteActiveSpan( ): Context { const propagationContext = propagationContextFromHeaders(sentryTrace, baggage); - // We store the DSC as OTEL trace state on the span context - const traceState = makeTraceState({ - parentSpanId: propagationContext.parentSpanId, - dsc: propagationContext.dsc, - sampled: propagationContext.sampled, - }); - - const spanContext: SpanContext = { - traceId: propagationContext.traceId, - spanId: propagationContext.parentSpanId || '', - isRemote: true, - traceFlags: propagationContext.sampled ? TraceFlags.SAMPLED : TraceFlags.NONE, - traceState, - }; - + const spanContext = generateSpanContextForPropagationContext(propagationContext); return trace.setSpanContext(ctx, spanContext); } diff --git a/packages/opentelemetry/src/trace.ts b/packages/opentelemetry/src/trace.ts index 356ba9a2688e..6ba41eec51e2 100644 --- a/packages/opentelemetry/src/trace.ts +++ b/packages/opentelemetry/src/trace.ts @@ -13,11 +13,12 @@ import { spanToJSON, } from '@sentry/core'; import type { Client, Scope, Span as SentrySpan } from '@sentry/types'; -import { continueTraceAsRemoteSpan, makeTraceState } from './propagator'; +import { continueTraceAsRemoteSpan } from './propagator'; import type { OpenTelemetryClient, OpenTelemetrySpanContext } from './types'; import { getContextFromScope, getScopesFromContext } from './utils/contextData'; import { getSamplingDecision } from './utils/getSamplingDecision'; +import { makeTraceState } from './utils/makeTraceState'; /** * Wraps a function with a transaction/span and finishes the span after the function is done. diff --git a/packages/opentelemetry/src/utils/generateSpanContextForPropagationContext.ts b/packages/opentelemetry/src/utils/generateSpanContextForPropagationContext.ts new file mode 100644 index 000000000000..d2aa470213f7 --- /dev/null +++ b/packages/opentelemetry/src/utils/generateSpanContextForPropagationContext.ts @@ -0,0 +1,27 @@ +import type { SpanContext } from '@opentelemetry/api'; +import { TraceFlags } from '@opentelemetry/api'; +import type { PropagationContext } from '@sentry/types'; +import { makeTraceState } from './makeTraceState'; + +/** + * Generates a SpanContext that represents a PropagationContext. + * This can be set on a `context` to make this a (virtual) active span. + */ +export function generateSpanContextForPropagationContext(propagationContext: PropagationContext): SpanContext { + // We store the DSC as OTEL trace state on the span context + const traceState = makeTraceState({ + parentSpanId: propagationContext.parentSpanId, + dsc: propagationContext.dsc, + sampled: propagationContext.sampled, + }); + + const spanContext: SpanContext = { + traceId: propagationContext.traceId, + spanId: propagationContext.parentSpanId || '', + isRemote: true, + traceFlags: propagationContext.sampled ? TraceFlags.SAMPLED : TraceFlags.NONE, + traceState, + }; + + return spanContext; +} diff --git a/packages/opentelemetry/src/utils/makeTraceState.ts b/packages/opentelemetry/src/utils/makeTraceState.ts new file mode 100644 index 000000000000..1b4fb4971efc --- /dev/null +++ b/packages/opentelemetry/src/utils/makeTraceState.ts @@ -0,0 +1,36 @@ +import { TraceState } from '@opentelemetry/core'; +import type { DynamicSamplingContext } from '@sentry/types'; +import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; +import { + SENTRY_TRACE_STATE_DSC, + SENTRY_TRACE_STATE_PARENT_SPAN_ID, + SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, +} from '../constants'; + +/** + * Generate a TraceState for the given data. + */ +export function makeTraceState({ + parentSpanId, + dsc, + sampled, +}: { + parentSpanId?: string; + dsc?: Partial; + sampled?: boolean; +}): TraceState { + // We store the DSC as OTEL trace state on the span context + const dscString = dsc ? dynamicSamplingContextToSentryBaggageHeader(dsc) : undefined; + + // We _always_ set the parent span ID, even if it is empty + // If we'd set this to 'undefined' we could not know if the trace state was set, but there was no parentSpanId, + // vs the trace state was not set at all (in which case we want to do fallback handling) + // If `''`, it should be considered "no parent" + const traceStateBase = new TraceState().set(SENTRY_TRACE_STATE_PARENT_SPAN_ID, parentSpanId || ''); + + const traceStateWithDsc = dscString ? traceStateBase.set(SENTRY_TRACE_STATE_DSC, dscString) : traceStateBase; + + // We also specifically want to store if this is sampled to be not recording, + // or unsampled (=could be either sampled or not) + return sampled === false ? traceStateWithDsc.set(SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, '1') : traceStateWithDsc; +} diff --git a/packages/opentelemetry/test/integration/transactions.test.ts b/packages/opentelemetry/test/integration/transactions.test.ts index fe03fc8a1030..b8e7713cb4ca 100644 --- a/packages/opentelemetry/test/integration/transactions.test.ts +++ b/packages/opentelemetry/test/integration/transactions.test.ts @@ -16,9 +16,9 @@ import { logger } from '@sentry/utils'; import { TraceState } from '@opentelemetry/core'; import { SENTRY_TRACE_STATE_DSC } from '../../src/constants'; -import { makeTraceState } from '../../src/propagator'; import { SentrySpanProcessor } from '../../src/spanProcessor'; import { startInactiveSpan, startSpan } from '../../src/trace'; +import { makeTraceState } from '../../src/utils/makeTraceState'; import type { TestClientInterface } from '../helpers/TestClient'; import { cleanupOtel, getProvider, mockSdkInit } from '../helpers/mockSdkInit'; diff --git a/packages/opentelemetry/test/propagator.test.ts b/packages/opentelemetry/test/propagator.test.ts index d3ee43f4d199..16848352239a 100644 --- a/packages/opentelemetry/test/propagator.test.ts +++ b/packages/opentelemetry/test/propagator.test.ts @@ -11,9 +11,10 @@ import { suppressTracing } from '@opentelemetry/core'; import { getCurrentScope, withScope } from '@sentry/core'; import { SENTRY_BAGGAGE_HEADER, SENTRY_SCOPES_CONTEXT_KEY, SENTRY_TRACE_HEADER } from '../src/constants'; -import { SentryPropagator, makeTraceState } from '../src/propagator'; +import { SentryPropagator } from '../src/propagator'; import { getScopesFromContext } from '../src/utils/contextData'; import { getSamplingDecision } from '../src/utils/getSamplingDecision'; +import { makeTraceState } from '../src/utils/makeTraceState'; import { cleanupOtel, mockSdkInit } from './helpers/mockSdkInit'; describe('SentryPropagator', () => { diff --git a/packages/opentelemetry/test/trace.test.ts b/packages/opentelemetry/test/trace.test.ts index 5d9329650969..979d47acb437 100644 --- a/packages/opentelemetry/test/trace.test.ts +++ b/packages/opentelemetry/test/trace.test.ts @@ -20,7 +20,6 @@ import { withScope, } from '@sentry/core'; import type { Event, Scope } from '@sentry/types'; -import { makeTraceState } from '../src/propagator'; import { SEMATTRS_HTTP_METHOD } from '@opentelemetry/semantic-conventions'; import { continueTrace, startInactiveSpan, startSpan, startSpanManual } from '../src/trace'; @@ -28,6 +27,7 @@ import type { AbstractSpan } from '../src/types'; import { getActiveSpan } from '../src/utils/getActiveSpan'; import { getSamplingDecision } from '../src/utils/getSamplingDecision'; import { getSpanKind } from '../src/utils/getSpanKind'; +import { makeTraceState } from '../src/utils/makeTraceState'; import { spanHasAttributes, spanHasName } from '../src/utils/spanTypes'; import { cleanupOtel, mockSdkInit } from './helpers/mockSdkInit';