Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(tracing): Send sample rate and type in transaction item header in envelope#3068
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { Event, TransactionSamplingMethod } from '@sentry/types'; | ||
| import { API } from '../../src/api'; | ||
| import { eventToSentryRequest } from '../../src/request'; | ||
| describe('eventToSentryRequest', () => { | ||
| const api = new API('https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012'); | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder how dogs can be bad at keeping secrets? 🤔 | ||
| const event: Event = { | ||
| contexts: { trace: { trace_id: '1231201211212012', span_id: '12261980', op: 'pageload' } }, | ||
| environment: 'dogpark', | ||
| event_id: '0908201304152013', | ||
| release: 'off.leash.park', | ||
| spans: [], | ||
| transaction: '/dogs/are/great/', | ||
| type: 'transaction', | ||
| user: { id: '1121', username: 'CharlieDog', ip_address: '11.21.20.12' }, | ||
| }; | ||
| [ | ||
| { method: TransactionSamplingMethod.Rate, rate: '0.1121', dog: 'Charlie' }, | ||
| { method: TransactionSamplingMethod.Sampler, rate: '0.1231', dog: 'Maisey' }, | ||
| { method: TransactionSamplingMethod.Inheritance, dog: 'Cory' }, | ||
| { method: TransactionSamplingMethod.Explicit, dog: 'Bodhi' }, | ||
| // this shouldn't ever happen (at least the method should always be included in tags), but good to know that things | ||
| // won't blow up if it does | ||
| { dog: 'Lucy' }, | ||
| ].forEach(({ method, rate, dog }) => { | ||
| it(`adds transaction sampling information to item header - ${method}, ${rate}, ${dog}`, () => { | ||
| // TODO kmclb - once tag types are loosened, don't need to cast to string here | ||
| event.tags = { __sentry_samplingMethod: String(method), __sentry_sampleRate: String(rate), dog }; | ||
| const result = eventToSentryRequest(event as Event, api); | ||
| const [envelopeHeaderString, itemHeaderString, eventString] = result.body.split('\n'); | ||
| const envelope = { | ||
| envelopeHeader: JSON.parse(envelopeHeaderString), | ||
| itemHeader: JSON.parse(itemHeaderString), | ||
| event: JSON.parse(eventString), | ||
| }; | ||
| // the right stuff is added to the item header | ||
| expect(envelope.itemHeader).toEqual({ | ||
| type: 'transaction', | ||
| // TODO kmclb - once tag types are loosened, don't need to cast to string here | ||
| sample_rates: [{ id: String(method), rate: String(rate) }], | ||
| }); | ||
| // show that it pops the right tags and leaves the rest alone | ||
| expect('__sentry_samplingMethod' in envelope.event.tags).toBe(false); | ||
| expect('__sentry_sampleRate' in envelope.event.tags).toBe(false); | ||
| expect('dog' in envelope.event.tags).toBe(true); | ||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import { getActiveDomain, getMainCarrier, Hub } from '@sentry/hub'; | ||
| import { CustomSamplingContext, SamplingContext, TransactionContext } from '@sentry/types'; | ||
| import { CustomSamplingContext, SamplingContext, TransactionContext, TransactionSamplingMethod } from '@sentry/types'; | ||
| import { | ||
| dynamicRequire, | ||
| extractNodeRequestData, | ||
| @@ -28,18 +28,6 @@ function traceHeaders(this: Hub): { [key: string]: string } { | ||
| return {}; | ||
| } | ||
| /** | ||
| * Implements sampling inheritance and falls back to user-provided static rate if no parent decision is available. | ||
| * | ||
| * @param parentSampled: The parent transaction's sampling decision, if any. | ||
| * @param givenRate: The rate to use if no parental decision is available. | ||
| * | ||
| * @returns The parent's sampling decision (if one exists), or the provided static rate | ||
| */ | ||
| function _inheritOrUseGivenRate(parentSampled: boolean | undefined, givenRate: unknown): boolean | unknown { | ||
| return parentSampled !== undefined ? parentSampled : givenRate; | ||
| } | ||
| /** | ||
| * Makes a sampling decision for the given transaction and stores it on the transaction. | ||
| * | ||
| @@ -64,15 +52,35 @@ function sample<T extends Transaction>(hub: Hub, transaction: T, samplingContext | ||
| // if the user has forced a sampling decision by passing a `sampled` value in their transaction context, go with that | ||
| if (transaction.sampled !== undefined) { | ||
| transaction.tags = { ...transaction.tags, __sentry_samplingMethod: TransactionSamplingMethod.Explicit }; | ||
| return transaction; | ||
| } | ||
| // we would have bailed already if neither `tracesSampler` nor `tracesSampleRate` were defined, so one of these should | ||
| // work; prefer the hook if so | ||
| const sampleRate = | ||
| typeof options.tracesSampler === 'function' | ||
| ? options.tracesSampler(samplingContext) | ||
| : _inheritOrUseGivenRate(samplingContext.parentSampled, options.tracesSampleRate); | ||
| let sampleRate; | ||
| if (typeof options.tracesSampler === 'function') { | ||
| sampleRate = options.tracesSampler(samplingContext); | ||
| // cast the rate to a number first in case it's a boolean | ||
| transaction.tags = { | ||
| ...transaction.tags, | ||
| __sentry_samplingMethod: TransactionSamplingMethod.Sampler, | ||
| // TODO kmclb - once tag types are loosened, don't need to cast to string here | ||
| __sentry_sampleRate: String(Number(sampleRate)), | ||
| }; | ||
| } else if (samplingContext.parentSampled !== undefined) { | ||
| sampleRate = samplingContext.parentSampled; | ||
| transaction.tags = { ...transaction.tags, __sentry_samplingMethod: TransactionSamplingMethod.Inheritance }; | ||
| } else { | ||
| sampleRate = options.tracesSampleRate; | ||
| // cast the rate to a number first in case it's a boolean | ||
| transaction.tags = { | ||
| ...transaction.tags, | ||
| __sentry_samplingMethod: TransactionSamplingMethod.Rate, | ||
| // TODO kmclb - once tag types are loosened, don't need to cast to string here | ||
| __sentry_sampleRate: String(Number(sampleRate)), | ||
| }; | ||
| } | ||
| // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The | ||
| // only valid values are booleans or numbers between 0 and 1.) | ||
| @@ -88,7 +96,7 @@ function sample<T extends Transaction>(hub: Hub, transaction: T, samplingContext | ||
| `[Tracing] Discarding transaction because ${ | ||
| typeof options.tracesSampler === 'function' | ||
| ? 'tracesSampler returned 0 or false' | ||
| : 'tracesSampleRate is set to 0' | ||
| : 'a negative sampling decision was inherited or tracesSampleRate is set to 0' | ||
lobsterkatie marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| }`, | ||
| ); | ||
| transaction.sampled = false; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍