diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/init.js new file mode 100644 index 000000000000..385a1cdf1df5 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/init.js @@ -0,0 +1,49 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ + idleTimeout: 3000, + finalTimeout: 3000, + childSpanTimeout: 3000, + }), + ], + ignoreSpans: [/ignore/], + tracesSampleRate: 1, + debug: true, +}); + +const waitFor = time => new Promise(resolve => setTimeout(resolve, time)); + +Sentry.startSpanManual( + { + name: 'take-me', + }, + async span => { + await waitFor(500); + span.end(); + }, +); + +Sentry.startSpanManual( + { + name: 'ignore-me', + }, + async span => { + await waitFor(1500); + span.end(); + }, +); + +Sentry.startSpanManual( + { + name: 'ignore-me-too', + }, + async span => { + await waitFor(2500); + span.end(); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/test.ts new file mode 100644 index 000000000000..bc752c9cdf41 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/test.ts @@ -0,0 +1,29 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; + +sentryTest( + 'adjusts the end timestamp of the root idle span if child spans are ignored', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const eventData = envelopeRequestParser(await pageloadRequestPromise); + + const { start_timestamp: startTimestamp, timestamp: endTimestamp } = eventData; + const durationSeconds = endTimestamp! - startTimestamp!; + + const spans = eventData.spans || []; + + expect(durationSeconds).toBeGreaterThan(0); + expect(durationSeconds).toBeLessThan(1.5); + + expect(spans.some(span => span.description === 'take-me')).toBe(true); + expect(spans.some(span => span.description?.includes('ignore-me'))).toBe(false); + }, +); diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 924bd1810ea3..6a7451fe96cb 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -1347,6 +1347,7 @@ function processBeforeSend( if (droppedSpans) { client.recordDroppedEvent('before_send', 'span', droppedSpans); } + processedEvent.spans = processedSpans; } } diff --git a/packages/core/src/tracing/idleSpan.ts b/packages/core/src/tracing/idleSpan.ts index 11045e0da1af..c8b37adec436 100644 --- a/packages/core/src/tracing/idleSpan.ts +++ b/packages/core/src/tracing/idleSpan.ts @@ -6,6 +6,7 @@ import type { Span } from '../types-hoist/span'; import type { StartSpanOptions } from '../types-hoist/startSpanOptions'; import { debug } from '../utils/debug-logger'; import { hasSpansEnabled } from '../utils/hasSpansEnabled'; +import { shouldIgnoreSpan } from '../utils/should-ignore-span'; import { _setSpanForScope } from '../utils/spanOnScope'; import { getActiveSpan, @@ -156,10 +157,21 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti return Reflect.apply(target, thisArg, [spanEndTimestamp, ...rest]); } - const childEndTimestamps = spans - .map(span => spanToJSON(span).timestamp) - .filter(timestamp => !!timestamp) as number[]; - const latestSpanEndTimestamp = childEndTimestamps.length ? Math.max(...childEndTimestamps) : undefined; + const ignoreSpans = client.getOptions().ignoreSpans; + + const latestSpanEndTimestamp = spans?.reduce((acc: number | undefined, current) => { + const currentSpanJson = spanToJSON(current); + if (!currentSpanJson.timestamp) { + return acc; + } + // Ignored spans will get dropped later (in the client) but since we already adjust + // the idle span end timestamp here, we can already take to-be-ignored spans out of + // the calculation here. + if (ignoreSpans && shouldIgnoreSpan(currentSpanJson, ignoreSpans)) { + return acc; + } + return acc ? Math.max(acc, currentSpanJson.timestamp) : currentSpanJson.timestamp; + }, undefined); // In reality this should always exist here, but type-wise it may be undefined... const spanStartTimestamp = spanToJSON(span).start_timestamp;