Skip to content
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

fix: Create spanRecorder whenever transactions are sampled #3255

Merged
merged 3 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 26 additions & 15 deletions packages/tracing/src/hubextensions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { getMainCarrier, Hub } from '@sentry/hub';
import { CustomSamplingContext, SamplingContext, TransactionContext, TransactionSamplingMethod } from '@sentry/types';
import {
CustomSamplingContext,
Options,
SamplingContext,
TransactionContext,
TransactionSamplingMethod,
} from '@sentry/types';
import { logger } from '@sentry/utils';

import { registerErrorInstrumentation } from './errors';
Expand Down Expand Up @@ -33,12 +39,9 @@ function traceHeaders(this: Hub): { [key: string]: string } {
*
* @returns The given transaction with its `sampled` value set
*/
function sample<T extends Transaction>(hub: Hub, transaction: T, samplingContext: SamplingContext): T {
const client = hub.getClient();
const options = (client && client.getOptions()) || {};

// nothing to do if there's no client or if tracing is disabled
if (!client || !hasTracingEnabled(options)) {
function sample<T extends Transaction>(transaction: T, options: Options, samplingContext: SamplingContext): T {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be a method on Transaction, but didn't want to make that change now to keep this patch small.

I did however change the argument order to have transaction as the first argument, since this function operates (mutates!) the transaction, and removed the dependency on the hub as we only need to know about the client options.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the options should go last though, but it's just a nitpick :P

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah agree, I'll leave that for later so we don't have to go change more lines on the call sites.

// nothing to do if tracing is not enabled
if (!hasTracingEnabled(options)) {
transaction.sampled = false;
return transaction;
}
Expand Down Expand Up @@ -114,10 +117,6 @@ function sample<T extends Transaction>(hub: Hub, transaction: T, samplingContext
return transaction;
}

// at this point we know we're keeping the transaction, whether because of an inherited decision or because it got
// lucky with the dice roll
transaction.initSpanRecorder(options._experiments?.maxSpans as number);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The culprit of #3254 was that in https://github.com/getsentry/sentry-javascript/pull/3255/files#diff-f5bf6e0cdf7709e5675fcdc3b4ff254dd68f3c9d1a399c8751e0fa1846fa85dbR54 we returned earlier and skipped initializing a span recorder.
Worked fine for forcing sampled: false, but clearly not the intended behavior for sampled: true.


logger.log(`[Tracing] starting ${transaction.op} transaction - ${transaction.name}`);
return transaction;
}
Expand Down Expand Up @@ -165,12 +164,18 @@ function _startTransaction(
transactionContext: TransactionContext,
customSamplingContext?: CustomSamplingContext,
): Transaction {
const transaction = new Transaction(transactionContext, this);
return sample(this, transaction, {
const options = this.getClient()?.getOptions() || {};

let transaction = new Transaction(transactionContext, this);
transaction = sample(transaction, options, {
parentSampled: transactionContext.parentSampled,
transactionContext,
...customSamplingContext,
});
if (transaction.sampled) {
transaction.initSpanRecorder(options._experiments?.maxSpans as number);
}
return transaction;
}

/**
Expand All @@ -183,12 +188,18 @@ export function startIdleTransaction(
onScope?: boolean,
customSamplingContext?: CustomSamplingContext,
): IdleTransaction {
const transaction = new IdleTransaction(transactionContext, hub, idleTimeout, onScope);
return sample(hub, transaction, {
const options = hub.getClient()?.getOptions() || {};

let transaction = new IdleTransaction(transactionContext, hub, idleTimeout, onScope);
transaction = sample(transaction, options, {
parentSampled: transactionContext.parentSampled,
transactionContext,
...customSamplingContext,
});
if (transaction.sampled) {
transaction.initSpanRecorder(options._experiments?.maxSpans as number);
}
return transaction;
}

/**
Expand Down
12 changes: 12 additions & 0 deletions packages/tracing/test/span.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,18 @@ describe('Span', () => {
expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext());
});

// See https://github.com/getsentry/sentry-javascript/issues/3254
test('finish a transaction + child span + sampled:true', () => {
const spy = jest.spyOn(hub as any, 'captureEvent') as any;
const transaction = hub.startTransaction({ name: 'test', op: 'parent', sampled: true });
const childSpan = transaction.startChild({ op: 'child' });
childSpan.finish();
transaction.finish();
expect(spy).toHaveBeenCalled();
expect(spy.mock.calls[0][0].spans).toHaveLength(1);
expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext());
});

test("finish a child span shouldn't trigger captureEvent", () => {
const spy = jest.spyOn(hub as any, 'captureEvent') as any;
const transaction = hub.startTransaction({ name: 'test' });
Expand Down