diff --git a/MIGRATION.md b/MIGRATION.md index f92022cc4690..49b906fc269f 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -10,6 +10,13 @@ npx @sentry/migr8@latest This will let you select which updates to run, and automatically update your code. Make sure to still review all code changes! +## Deprecated `transactionContext` passed to `tracesSampler` + +Instead of an `transactionContext` being passed to the `tracesSampler` callback, the callback will directly receive +`name` and `attributes` going forward. You can use these to make your sampling decisions, while `transactionContext` +will be removed in v8. Note that the `attributes` are only the attributes at transaction creation time, and some +attributes may only be set later (and thus not be available during sampling). + ## Deprecate using `getClient()` to check if the SDK was initialized In v8, `getClient()` will stop returning `undefined` if `Sentry.init()` was not called. For cases where this may be used diff --git a/packages/core/src/tracing/hubextensions.ts b/packages/core/src/tracing/hubextensions.ts index e35543f16631..51748ea72351 100644 --- a/packages/core/src/tracing/hubextensions.ts +++ b/packages/core/src/tracing/hubextensions.ts @@ -65,8 +65,14 @@ The transaction will not be sampled. Please use the ${configInstrumenter} instru // eslint-disable-next-line deprecation/deprecation let transaction = new Transaction(transactionContext, this); transaction = sampleTransaction(transaction, options, { + name: transactionContext.name, parentSampled: transactionContext.parentSampled, transactionContext, + attributes: { + // eslint-disable-next-line deprecation/deprecation + ...transactionContext.data, + ...transactionContext.attributes, + }, ...customSamplingContext, }); if (transaction.isRecording()) { @@ -106,8 +112,14 @@ export function startIdleTransaction( delayAutoFinishUntilSignal, ); transaction = sampleTransaction(transaction, options, { + name: transactionContext.name, parentSampled: transactionContext.parentSampled, transactionContext, + attributes: { + // eslint-disable-next-line deprecation/deprecation + ...transactionContext.data, + ...transactionContext.attributes, + }, ...customSamplingContext, }); if (transaction.isRecording()) { diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index fca086f10c94..7cb09e1569ec 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -4,6 +4,7 @@ import { addTracingExtensions, getCurrentScope, makeMain, + setCurrentClient, spanToJSON, withScope, } from '../../../src'; @@ -357,6 +358,35 @@ describe('startSpan', () => { expect(span).toBeDefined(); }); }); + + it('samples with a tracesSampler', () => { + const tracesSampler = jest.fn(() => { + return true; + }); + + const options = getDefaultTestClientOptions({ tracesSampler }); + client = new TestClient(options); + setCurrentClient(client); + + startSpan( + { name: 'outer', attributes: { test1: 'aa', test2: 'aa' }, data: { test1: 'bb', test3: 'bb' } }, + outerSpan => { + expect(outerSpan).toBeDefined(); + }, + ); + + expect(tracesSampler).toBeCalledTimes(1); + expect(tracesSampler).toHaveBeenLastCalledWith({ + parentSampled: undefined, + name: 'outer', + attributes: { + test1: 'aa', + test2: 'aa', + test3: 'bb', + }, + transactionContext: expect.objectContaining({ name: 'outer', parentSampled: undefined }), + }); + }); }); describe('startSpanManual', () => { diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index 1ca4e3584b1f..d14d77010422 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -79,6 +79,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor { const transaction = getCurrentHub().startTransaction({ name: otelSpan.name, ...traceCtx, + attributes: otelSpan.attributes, instrumenter: 'otel', startTimestamp: convertOtelTimeToSeconds(otelSpan.startTime), spanId: otelSpanId, diff --git a/packages/opentelemetry/src/sampler.ts b/packages/opentelemetry/src/sampler.ts index a3f2d07eddd4..0cd15ebb2daf 100644 --- a/packages/opentelemetry/src/sampler.ts +++ b/packages/opentelemetry/src/sampler.ts @@ -3,7 +3,7 @@ import type { Attributes, Context, SpanContext } from '@opentelemetry/api'; import { TraceFlags, isSpanContextValid, trace } from '@opentelemetry/api'; import type { Sampler, SamplingResult } from '@opentelemetry/sdk-trace-base'; import { SamplingDecision } from '@opentelemetry/sdk-trace-base'; -import { hasTracingEnabled } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, hasTracingEnabled } from '@sentry/core'; import type { Client, ClientOptions, SamplingContext } from '@sentry/types'; import { isNaN, logger } from '@sentry/utils'; @@ -27,7 +27,7 @@ export class SentrySampler implements Sampler { traceId: string, spanName: string, _spanKind: unknown, - _attributes: unknown, + spanAttributes: unknown, _links: unknown, ): SamplingResult { const options = this._client.getOptions(); @@ -54,6 +54,8 @@ export class SentrySampler implements Sampler { } const sampleRate = getSampleRate(options, { + name: spanName, + attributes: spanAttributes, transactionContext: { name: spanName, parentSampled, @@ -62,7 +64,7 @@ export class SentrySampler implements Sampler { }); const attributes: Attributes = { - [InternalSentrySemanticAttributes.SAMPLE_RATE]: Number(sampleRate), + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: Number(sampleRate), }; if (typeof parentSampled === 'boolean') { diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index 0d57d1009e31..f4bf4bc3aec4 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -3,7 +3,7 @@ import type { ExportResult } from '@opentelemetry/core'; import { ExportResultCode } from '@opentelemetry/core'; import type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import { flush, getCurrentScope } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, flush, getCurrentScope } from '@sentry/core'; import type { Scope, Span as SentrySpan, SpanOrigin, TransactionSource } from '@sentry/types'; import { logger } from '@sentry/utils'; @@ -176,7 +176,7 @@ function createTransactionForOtelSpan(span: ReadableSpan): OpenTelemetryTransact metadata: { dynamicSamplingContext, source, - sampleRate: span.attributes[InternalSentrySemanticAttributes.SAMPLE_RATE] as number | undefined, + sampleRate: span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE] as number | undefined, ...metadata, }, data: removeSentryAttributes(data), @@ -267,7 +267,7 @@ function removeSentryAttributes(data: Record): Record { span => { expect(span).toBeDefined(); expect(getSpanAttributes(span)).toEqual({ - [InternalSentrySemanticAttributes.SAMPLE_RATE]: 1, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, }); expect(getSpanMetadata(span)).toEqual(undefined); @@ -227,7 +228,7 @@ describe('trace', () => { [InternalSentrySemanticAttributes.SOURCE]: 'task', [InternalSentrySemanticAttributes.ORIGIN]: 'auto.test.origin', [InternalSentrySemanticAttributes.OP]: 'my-op', - [InternalSentrySemanticAttributes.SAMPLE_RATE]: 1, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, }); expect(getSpanMetadata(span)).toEqual({ requestPath: 'test-path' }); @@ -253,7 +254,7 @@ describe('trace', () => { expect(span).toBeDefined(); expect(getSpanName(span)).toEqual('outer'); expect(getSpanAttributes(span)).toEqual({ - [InternalSentrySemanticAttributes.SAMPLE_RATE]: 1, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, test1: 'test 1', test2: 2, }); @@ -326,7 +327,7 @@ describe('trace', () => { expect(span).toBeDefined(); expect(getSpanAttributes(span)).toEqual({ - [InternalSentrySemanticAttributes.SAMPLE_RATE]: 1, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, }); expect(getSpanMetadata(span)).toEqual(undefined); @@ -341,7 +342,7 @@ describe('trace', () => { expect(span2).toBeDefined(); expect(getSpanAttributes(span2)).toEqual({ - [InternalSentrySemanticAttributes.SAMPLE_RATE]: 1, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, [InternalSentrySemanticAttributes.SOURCE]: 'task', [InternalSentrySemanticAttributes.ORIGIN]: 'auto.test.origin', [InternalSentrySemanticAttributes.OP]: 'my-op', @@ -366,7 +367,7 @@ describe('trace', () => { expect(span).toBeDefined(); expect(getSpanName(span)).toEqual('outer'); expect(getSpanAttributes(span)).toEqual({ - [InternalSentrySemanticAttributes.SAMPLE_RATE]: 1, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, test1: 'test 1', test2: 2, }); @@ -451,7 +452,7 @@ describe('trace', () => { expect(span).toBeDefined(); expect(getSpanName(span)).toEqual('outer'); expect(getSpanAttributes(span)).toEqual({ - [InternalSentrySemanticAttributes.SAMPLE_RATE]: 1, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, test1: 'test 1', test2: 2, }); @@ -688,6 +689,10 @@ describe('trace (sampling)', () => { expect(tracesSampler).toBeCalledTimes(1); expect(tracesSampler).toHaveBeenLastCalledWith({ parentSampled: undefined, + name: 'outer', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + }, transactionContext: { name: 'outer', parentSampled: undefined }, }); @@ -705,6 +710,8 @@ describe('trace (sampling)', () => { expect(tracesSampler).toHaveBeenCalledTimes(3); expect(tracesSampler).toHaveBeenLastCalledWith({ parentSampled: false, + name: 'inner2', + attributes: {}, transactionContext: { name: 'inner2', parentSampled: false }, }); }); @@ -727,6 +734,10 @@ describe('trace (sampling)', () => { expect(tracesSampler).toBeCalledTimes(1); expect(tracesSampler).toHaveBeenLastCalledWith({ parentSampled: undefined, + name: 'outer', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + }, transactionContext: { name: 'outer', parentSampled: undefined }, }); @@ -744,6 +755,8 @@ describe('trace (sampling)', () => { expect(tracesSampler).toHaveBeenCalledTimes(3); expect(tracesSampler).toHaveBeenLastCalledWith({ parentSampled: false, + name: 'inner2', + attributes: {}, transactionContext: { name: 'inner2', parentSampled: false }, }); @@ -757,6 +770,8 @@ describe('trace (sampling)', () => { expect(tracesSampler).toHaveBeenCalledTimes(4); expect(tracesSampler).toHaveBeenLastCalledWith({ parentSampled: undefined, + name: 'outer3', + attributes: {}, transactionContext: { name: 'outer3', parentSampled: undefined }, }); }); @@ -799,6 +814,8 @@ describe('trace (sampling)', () => { expect(tracesSampler).toBeCalledTimes(1); expect(tracesSampler).toHaveBeenLastCalledWith({ parentSampled: true, + name: 'outer', + attributes: {}, transactionContext: { name: 'outer', parentSampled: true,