-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): Emit client reports for unsampled root spans on span start (
#14936) With this PR, the `sample_rate`,`transaction` client report is now consistently emitted at the time when the root span is sampled. Previously, in Node (OTEL) we did not emit this at all, while in browser we emit it at span end time. This is inconsistent and not ideal. Emitting it in OTEL at span end time is difficult because `span.end()` is a no-op there and you do not get access to it anywhere. So doing it at span start (=sample time) is easier, and also makes more sense as this is also actually the time when the span is dropped.
- Loading branch information
Showing
15 changed files
with
181 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import { SpanKind, context, trace } from '@opentelemetry/api'; | ||
import { TraceState } from '@opentelemetry/core'; | ||
import { SamplingDecision } from '@opentelemetry/sdk-trace-base'; | ||
import { ATTR_HTTP_REQUEST_METHOD } from '@opentelemetry/semantic-conventions'; | ||
import { generateSpanId, generateTraceId } from '@sentry/core'; | ||
import { SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING } from '../src/constants'; | ||
import { SentrySampler } from '../src/sampler'; | ||
import { TestClient, getDefaultTestClientOptions } from './helpers/TestClient'; | ||
import { cleanupOtel } from './helpers/mockSdkInit'; | ||
|
||
describe('SentrySampler', () => { | ||
afterEach(() => { | ||
cleanupOtel(); | ||
}); | ||
|
||
it('works with tracesSampleRate=0', () => { | ||
const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 0 })); | ||
const spyOnDroppedEvent = jest.spyOn(client, 'recordDroppedEvent'); | ||
const sampler = new SentrySampler(client); | ||
|
||
const ctx = context.active(); | ||
const traceId = generateTraceId(); | ||
const spanName = 'test'; | ||
const spanKind = SpanKind.INTERNAL; | ||
const spanAttributes = {}; | ||
const links = undefined; | ||
|
||
const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links); | ||
expect(actual).toEqual({ | ||
decision: SamplingDecision.NOT_RECORD, | ||
attributes: { 'sentry.sample_rate': 0 }, | ||
traceState: new TraceState().set('sentry.sampled_not_recording', '1'), | ||
}); | ||
expect(spyOnDroppedEvent).toHaveBeenCalledTimes(1); | ||
expect(spyOnDroppedEvent).toHaveBeenCalledWith('sample_rate', 'transaction'); | ||
|
||
spyOnDroppedEvent.mockReset(); | ||
}); | ||
|
||
it('works with tracesSampleRate=0 & for a child span', () => { | ||
const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 0 })); | ||
const spyOnDroppedEvent = jest.spyOn(client, 'recordDroppedEvent'); | ||
const sampler = new SentrySampler(client); | ||
|
||
const traceId = generateTraceId(); | ||
const ctx = trace.setSpanContext(context.active(), { | ||
spanId: generateSpanId(), | ||
traceId, | ||
traceFlags: 0, | ||
traceState: new TraceState().set(SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, '1'), | ||
}); | ||
const spanName = 'test'; | ||
const spanKind = SpanKind.INTERNAL; | ||
const spanAttributes = {}; | ||
const links = undefined; | ||
|
||
const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links); | ||
expect(actual).toEqual({ | ||
decision: SamplingDecision.NOT_RECORD, | ||
attributes: { 'sentry.sample_rate': 0 }, | ||
traceState: new TraceState().set(SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, '1'), | ||
}); | ||
expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0); | ||
|
||
spyOnDroppedEvent.mockReset(); | ||
}); | ||
|
||
it('works with tracesSampleRate=1', () => { | ||
const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 1 })); | ||
const spyOnDroppedEvent = jest.spyOn(client, 'recordDroppedEvent'); | ||
const sampler = new SentrySampler(client); | ||
|
||
const ctx = context.active(); | ||
const traceId = generateTraceId(); | ||
const spanName = 'test'; | ||
const spanKind = SpanKind.INTERNAL; | ||
const spanAttributes = {}; | ||
const links = undefined; | ||
|
||
const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links); | ||
expect(actual).toEqual({ | ||
decision: SamplingDecision.RECORD_AND_SAMPLED, | ||
attributes: { 'sentry.sample_rate': 1 }, | ||
traceState: new TraceState(), | ||
}); | ||
expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0); | ||
|
||
spyOnDroppedEvent.mockReset(); | ||
}); | ||
|
||
it('works with traceSampleRate=undefined', () => { | ||
const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: undefined })); | ||
const spyOnDroppedEvent = jest.spyOn(client, 'recordDroppedEvent'); | ||
const sampler = new SentrySampler(client); | ||
|
||
const ctx = context.active(); | ||
const traceId = generateTraceId(); | ||
const spanName = 'test'; | ||
const spanKind = SpanKind.INTERNAL; | ||
const spanAttributes = {}; | ||
const links = undefined; | ||
|
||
const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links); | ||
expect(actual).toEqual({ | ||
decision: SamplingDecision.NOT_RECORD, | ||
traceState: new TraceState(), | ||
}); | ||
expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0); | ||
|
||
spyOnDroppedEvent.mockReset(); | ||
}); | ||
|
||
it('ignores local http client root spans', () => { | ||
const client = new TestClient(getDefaultTestClientOptions({ tracesSampleRate: 0 })); | ||
const spyOnDroppedEvent = jest.spyOn(client, 'recordDroppedEvent'); | ||
const sampler = new SentrySampler(client); | ||
|
||
const ctx = context.active(); | ||
const traceId = generateTraceId(); | ||
const spanName = 'test'; | ||
const spanKind = SpanKind.CLIENT; | ||
const spanAttributes = { | ||
[ATTR_HTTP_REQUEST_METHOD]: 'GET', | ||
}; | ||
const links = undefined; | ||
|
||
const actual = sampler.shouldSample(ctx, traceId, spanName, spanKind, spanAttributes, links); | ||
expect(actual).toEqual({ | ||
decision: SamplingDecision.NOT_RECORD, | ||
traceState: new TraceState(), | ||
}); | ||
expect(spyOnDroppedEvent).toHaveBeenCalledTimes(0); | ||
|
||
spyOnDroppedEvent.mockReset(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.