Skip to content

Commit 5ac0fca

Browse files
authored
fix(tracing): Allow unsampled transactions to be findable by getTransaction() (#2952)
Adds a pointer to each span, pointing at that span's enclosing transaction, and changes `getTransaction()` to use it.
1 parent bead28c commit 5ac0fca

File tree

6 files changed

+39
-16
lines changed

6 files changed

+39
-16
lines changed

packages/hub/src/scope.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,20 @@ export class Scope implements ScopeInterface {
217217
* @inheritDoc
218218
*/
219219
public getTransaction(): Transaction | undefined {
220-
const span = this.getSpan() as Span & { spanRecorder: { spans: Span[] } };
221-
if (span && span.spanRecorder && span.spanRecorder.spans[0]) {
220+
// often, this span will be a transaction, but it's not guaranteed to be
221+
const span = this.getSpan() as undefined | (Span & { spanRecorder: { spans: Span[] } });
222+
223+
// try it the new way first
224+
if (span?.transaction) {
225+
return span?.transaction;
226+
}
227+
228+
// fallback to the old way (known bug: this only finds transactions with sampled = true)
229+
if (span?.spanRecorder?.spans[0]) {
222230
return span.spanRecorder.spans[0] as Transaction;
223231
}
232+
233+
// neither way found a transaction
224234
return undefined;
225235
}
226236

packages/tracing/src/span.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable max-lines */
2-
import { Span as SpanInterface, SpanContext } from '@sentry/types';
2+
import { Span as SpanInterface, SpanContext, Transaction } from '@sentry/types';
33
import { dropUndefinedKeys, timestampWithMs, uuid4 } from '@sentry/utils';
44

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

102+
/**
103+
* @inheritDoc
104+
*/
105+
public transaction?: Transaction;
106+
102107
/**
103108
* You should never call the constructor manually, always use `hub.startSpan()`.
104109
* @internal
@@ -161,19 +166,21 @@ export class Span implements SpanInterface, SpanContext {
161166
public startChild(
162167
spanContext?: Pick<SpanContext, Exclude<keyof SpanContext, 'spanId' | 'sampled' | 'traceId' | 'parentSpanId'>>,
163168
): Span {
164-
const span = new Span({
169+
const childSpan = new Span({
165170
...spanContext,
166171
parentSpanId: this.spanId,
167172
sampled: this.sampled,
168173
traceId: this.traceId,
169174
});
170175

171-
span.spanRecorder = this.spanRecorder;
172-
if (span.spanRecorder) {
173-
span.spanRecorder.add(span);
176+
childSpan.spanRecorder = this.spanRecorder;
177+
if (childSpan.spanRecorder) {
178+
childSpan.spanRecorder.add(childSpan);
174179
}
175180

176-
return span;
181+
childSpan.transaction = this.transaction;
182+
183+
return childSpan;
177184
}
178185

179186
/**

packages/tracing/src/transaction.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ export class Transaction extends SpanClass implements TransactionInterface {
3232
this.name = transactionContext.name ? transactionContext.name : '';
3333

3434
this._trimEnd = transactionContext.trimEnd;
35+
36+
// this is because transactions are also spans, and spans have a transaction pointer
37+
this.transaction = this;
3538
}
3639

3740
/**

packages/tracing/test/browser/browsertracing.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,7 @@ describe('BrowserTracing', () => {
178178
expect(mockBeforeNavigation).toHaveBeenCalledTimes(1);
179179
});
180180

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

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

packages/tracing/test/hub.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ describe('Hub', () => {
4444
expect(hub.getScope()?.getTransaction()).toBe(transaction);
4545
});
4646

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

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

387-
// TODO add this back in once getTransaction() returns sampled = false transactions, too
388-
it.skip('should propagate negative sampling decision to child transactions in XHR header', () => {
386+
it('should propagate negative sampling decision to child transactions in XHR header', () => {
389387
const hub = new Hub(
390388
new BrowserClient({
391389
dsn: 'https://[email protected]/1121',

packages/types/src/span.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Transaction } from './transaction';
2+
13
/** Interface holding all properties that can be set on a Span on creation. */
24
export interface SpanContext {
35
/**
@@ -84,6 +86,11 @@ export interface Span extends SpanContext {
8486
*/
8587
data: { [key: string]: any };
8688

89+
/**
90+
* The transaction containing this span
91+
*/
92+
transaction?: Transaction;
93+
8794
/**
8895
* Sets the finish timestamp on the current span.
8996
* @param endTimestamp Takes an endTimestamp if the end should not be the time when you call this function.

0 commit comments

Comments
 (0)