Skip to content

fix(tracing): Allow unsampled transactions to be findable by getTransaction() #2952

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

Merged
merged 5 commits into from
Oct 2, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions packages/hub/src/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,20 @@ export class Scope implements ScopeInterface {
* @inheritDoc
*/
public getTransaction(): Transaction | undefined {
const span = this.getSpan() as Span & { spanRecorder: { spans: Span[] } };
if (span && span.spanRecorder && span.spanRecorder.spans[0]) {
// often, this span will be a transaction, but it's not guaranteed to be
const span = this.getSpan() as undefined | (Span & { spanRecorder: { spans: Span[] } });

// try it the new way first
if (span?.transaction) {
return span?.transaction;
}

// fallback to the old way (known bug: this only finds transactions with sampled = true)
if (span?.spanRecorder?.spans[0]) {
return span.spanRecorder.spans[0] as Transaction;
}

// neither way found a transaction
return undefined;
}

Expand Down
19 changes: 13 additions & 6 deletions packages/tracing/src/span.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable max-lines */
import { Span as SpanInterface, SpanContext } from '@sentry/types';
import { Span as SpanInterface, SpanContext, Transaction } from '@sentry/types';
import { dropUndefinedKeys, timestampWithMs, uuid4 } from '@sentry/utils';

import { SpanStatus } from './spanstatus';
Expand Down Expand Up @@ -99,6 +99,11 @@ export class Span implements SpanInterface, SpanContext {
*/
public spanRecorder?: SpanRecorder;

/**
* @inheritDoc
*/
public transaction?: Transaction;

/**
* You should never call the constructor manually, always use `hub.startSpan()`.
* @internal
Expand Down Expand Up @@ -161,19 +166,21 @@ export class Span implements SpanInterface, SpanContext {
public startChild(
spanContext?: Pick<SpanContext, Exclude<keyof SpanContext, 'spanId' | 'sampled' | 'traceId' | 'parentSpanId'>>,
): Span {
const span = new Span({
const childSpan = new Span({
...spanContext,
parentSpanId: this.spanId,
sampled: this.sampled,
traceId: this.traceId,
});

span.spanRecorder = this.spanRecorder;
if (span.spanRecorder) {
span.spanRecorder.add(span);
childSpan.spanRecorder = this.spanRecorder;
if (childSpan.spanRecorder) {
childSpan.spanRecorder.add(childSpan);
}

return span;
childSpan.transaction = this.transaction;

return childSpan;
}

/**
Expand Down
3 changes: 3 additions & 0 deletions packages/tracing/src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export class Transaction extends SpanClass implements TransactionInterface {
this.name = transactionContext.name ? transactionContext.name : '';

this._trimEnd = transactionContext.trimEnd;

// this is because transactions are also spans, and spans have a transaction pointer
this.transaction = this;
}

/**
Expand Down
6 changes: 2 additions & 4 deletions packages/tracing/test/browser/browsertracing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,7 @@ describe('BrowserTracing', () => {
expect(mockBeforeNavigation).toHaveBeenCalledTimes(1);
});

// TODO add this back in once getTransaction() returns sampled = false transactions, too
it.skip('creates a transaction with sampled = false if it returns undefined', () => {
it('creates a transaction with sampled = false if beforeNavigate returns undefined', () => {
const mockBeforeNavigation = jest.fn().mockReturnValue(undefined);
createBrowserTracing(true, {
beforeNavigate: mockBeforeNavigation,
Expand Down Expand Up @@ -412,8 +411,7 @@ describe('BrowserTracing', () => {
});

describe('using the data', () => {
// TODO add this back in once getTransaction() returns sampled = false transactions, too
it.skip('uses the data for pageload transactions', () => {
it('uses the data for pageload transactions', () => {
// make sampled false here, so we can see that it's being used rather than the tracesSampleRate-dictated one
document.head.innerHTML = `<meta name="sentry-trace" content="12312012123120121231201212312012-1121201211212012-0">`;

Expand Down
6 changes: 2 additions & 4 deletions packages/tracing/test/hub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ describe('Hub', () => {
expect(hub.getScope()?.getTransaction()).toBe(transaction);
});

// TODO add this back in once getTransaction() returns sampled = false transactions, too
it.skip('should find a transaction which has been set on the scope if sampled = false', () => {
it('should find a transaction which has been set on the scope if sampled = false', () => {
const hub = new Hub(new BrowserClient({ tracesSampleRate: 1 }));
const transaction = hub.startTransaction({ name: 'dogpark', sampled: false });

Expand Down Expand Up @@ -384,8 +383,7 @@ describe('Hub', () => {
expect(extractTraceparentData(headers['sentry-trace'])!.parentSampled).toBe(true);
});

// TODO add this back in once getTransaction() returns sampled = false transactions, too
it.skip('should propagate negative sampling decision to child transactions in XHR header', () => {
it('should propagate negative sampling decision to child transactions in XHR header', () => {
const hub = new Hub(
new BrowserClient({
dsn: 'https://[email protected]/1121',
Expand Down
7 changes: 7 additions & 0 deletions packages/types/src/span.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Transaction } from './transaction';

/** Interface holding all properties that can be set on a Span on creation. */
export interface SpanContext {
/**
Expand Down Expand Up @@ -84,6 +86,11 @@ export interface Span extends SpanContext {
*/
data: { [key: string]: any };

/**
* The transaction containing this span
*/
transaction?: Transaction;

/**
* Sets the finish timestamp on the current span.
* @param endTimestamp Takes an endTimestamp if the end should not be the time when you call this function.
Expand Down