Skip to content

Commit 1e23e90

Browse files
authored
feat(tracing): Add transaction.setContext method (#6154)
1 parent d2dd7e6 commit 1e23e90

File tree

4 files changed

+162
-6
lines changed

4 files changed

+162
-6
lines changed

packages/opentelemetry-node/test/spanprocessor.test.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { SemanticAttributes, SemanticResourceAttributes } from '@opentelemetry/s
77
import { createTransport, Hub, makeMain } from '@sentry/core';
88
import { NodeClient } from '@sentry/node';
99
import { addExtensionMethods, Span as SentrySpan, SpanStatusType, Transaction } from '@sentry/tracing';
10-
import { Contexts, Scope } from '@sentry/types';
10+
import { Scope } from '@sentry/types';
1111
import { resolvedSyncPromise } from '@sentry/utils';
1212

1313
import { SENTRY_SPAN_PROCESSOR_MAP, SentrySpanProcessor } from '../src/spanprocessor';
@@ -55,21 +55,22 @@ describe('SentrySpanProcessor', () => {
5555
}
5656

5757
function getContext(transaction: Transaction) {
58-
const transactionWithContext = transaction as unknown as Transaction & { _contexts: Contexts };
58+
const transactionWithContext = transaction as unknown as Transaction;
59+
// @ts-ignore accessing private property
5960
return transactionWithContext._contexts;
6061
}
6162

6263
// monkey-patch finish to store the context at finish time
6364
function monkeyPatchTransactionFinish(transaction: Transaction) {
64-
const monkeyPatchedTransaction = transaction as Transaction & { _contexts: Contexts };
65+
const monkeyPatchedTransaction = transaction as Transaction;
6566

6667
// eslint-disable-next-line @typescript-eslint/unbound-method
6768
const originalFinish = monkeyPatchedTransaction.finish;
69+
// @ts-ignore accessing private property
6870
monkeyPatchedTransaction._contexts = {};
6971
monkeyPatchedTransaction.finish = function (endTimestamp?: number | undefined) {
70-
monkeyPatchedTransaction._contexts = (
71-
transaction._hub.getScope() as unknown as Scope & { _contexts: Contexts }
72-
)._contexts;
72+
// @ts-ignore accessing private property
73+
monkeyPatchedTransaction._contexts = (transaction._hub.getScope() as unknown as Scope)._contexts;
7374

7475
return originalFinish.apply(monkeyPatchedTransaction, [endTimestamp]);
7576
};

packages/tracing/src/transaction.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { getCurrentHub, Hub } from '@sentry/core';
22
import {
3+
Context,
4+
Contexts,
35
DynamicSamplingContext,
46
Event,
57
Measurements,
@@ -25,6 +27,8 @@ export class Transaction extends SpanClass implements TransactionInterface {
2527

2628
private _measurements: Measurements = {};
2729

30+
private _contexts: Contexts = {};
31+
2832
private _trimEnd?: boolean;
2933

3034
private _frozenDynamicSamplingContext: Readonly<Partial<DynamicSamplingContext>> | undefined = undefined;
@@ -105,6 +109,18 @@ export class Transaction extends SpanClass implements TransactionInterface {
105109
this.spanRecorder.add(this);
106110
}
107111

112+
/**
113+
* @inheritDoc
114+
*/
115+
public setContext(key: string, context: Context | null): void {
116+
if (context === null) {
117+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
118+
delete this._contexts[key];
119+
} else {
120+
this._contexts[key] = context;
121+
}
122+
}
123+
108124
/**
109125
* @inheritDoc
110126
*/
@@ -163,6 +179,8 @@ export class Transaction extends SpanClass implements TransactionInterface {
163179

164180
const transaction: Event = {
165181
contexts: {
182+
...this._contexts,
183+
// We don't want to override trace context
166184
trace: this.getTraceContext(),
167185
},
168186
spans: finishedSpans,

packages/tracing/test/transaction.test.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
import { BrowserClient, Hub } from '@sentry/browser';
2+
3+
import { addExtensionMethods } from '../src';
14
import { Transaction } from '../src/transaction';
5+
import { getDefaultBrowserClientOptions } from './testutils';
26

37
describe('`Transaction` class', () => {
8+
beforeAll(() => {
9+
addExtensionMethods();
10+
});
11+
412
describe('transaction name source', () => {
513
it('sets source in constructor if provided', () => {
614
const transaction = new Transaction({ name: 'dogpark', metadata: { source: 'route' } });
@@ -192,4 +200,127 @@ describe('`Transaction` class', () => {
192200
});
193201
});
194202
});
203+
204+
describe('setContext', () => {
205+
it('sets context', () => {
206+
const transaction = new Transaction({ name: 'dogpark' });
207+
transaction.setContext('foo', {
208+
key: 'val',
209+
key2: 'val2',
210+
});
211+
212+
// @ts-ignore accessing private property
213+
expect(transaction._contexts).toEqual({
214+
foo: {
215+
key: 'val',
216+
key2: 'val2',
217+
},
218+
});
219+
});
220+
221+
it('overwrites context', () => {
222+
const transaction = new Transaction({ name: 'dogpark' });
223+
transaction.setContext('foo', {
224+
key: 'val',
225+
key2: 'val2',
226+
});
227+
transaction.setContext('foo', {
228+
key3: 'val3',
229+
});
230+
231+
// @ts-ignore accessing private property
232+
expect(transaction._contexts).toEqual({
233+
foo: {
234+
key3: 'val3',
235+
},
236+
});
237+
});
238+
239+
it('merges context', () => {
240+
const transaction = new Transaction({ name: 'dogpark' });
241+
transaction.setContext('foo', {
242+
key: 'val',
243+
key2: 'val2',
244+
});
245+
transaction.setContext('bar', {
246+
anotherKey: 'anotherVal',
247+
});
248+
249+
// @ts-ignore accessing private property
250+
expect(transaction._contexts).toEqual({
251+
foo: {
252+
key: 'val',
253+
key2: 'val2',
254+
},
255+
bar: {
256+
anotherKey: 'anotherVal',
257+
},
258+
});
259+
});
260+
261+
it('deletes context', () => {
262+
const transaction = new Transaction({ name: 'dogpark' });
263+
transaction.setContext('foo', {
264+
key: 'val',
265+
key2: 'val2',
266+
});
267+
transaction.setContext('foo', null);
268+
269+
// @ts-ignore accessing private property
270+
expect(transaction._contexts).toEqual({});
271+
});
272+
273+
it('sets contexts on the event', () => {
274+
const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 });
275+
const client = new BrowserClient(options);
276+
const hub = new Hub(client);
277+
278+
jest.spyOn(hub, 'captureEvent');
279+
280+
const transaction = hub.startTransaction({ name: 'dogpark' });
281+
transaction.setContext('foo', { key: 'val' });
282+
transaction.finish();
283+
284+
// eslint-disable-next-line @typescript-eslint/unbound-method
285+
expect(hub.captureEvent).toHaveBeenCalledTimes(1);
286+
// eslint-disable-next-line @typescript-eslint/unbound-method
287+
expect(hub.captureEvent).toHaveBeenLastCalledWith(
288+
expect.objectContaining({
289+
contexts: {
290+
foo: { key: 'val' },
291+
trace: {
292+
span_id: transaction.spanId,
293+
trace_id: transaction.traceId,
294+
},
295+
},
296+
}),
297+
);
298+
});
299+
300+
it('does not override trace context', () => {
301+
const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 });
302+
const client = new BrowserClient(options);
303+
const hub = new Hub(client);
304+
305+
jest.spyOn(hub, 'captureEvent');
306+
307+
const transaction = hub.startTransaction({ name: 'dogpark' });
308+
transaction.setContext('trace', { key: 'val' });
309+
transaction.finish();
310+
311+
// eslint-disable-next-line @typescript-eslint/unbound-method
312+
expect(hub.captureEvent).toHaveBeenCalledTimes(1);
313+
// eslint-disable-next-line @typescript-eslint/unbound-method
314+
expect(hub.captureEvent).toHaveBeenLastCalledWith(
315+
expect.objectContaining({
316+
contexts: {
317+
trace: {
318+
span_id: transaction.spanId,
319+
trace_id: transaction.traceId,
320+
},
321+
},
322+
}),
323+
);
324+
});
325+
});
195326
});

packages/types/src/transaction.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Context } from './context';
12
import { DynamicSamplingContext } from './envelope';
23
import { Instrumenter } from './instrumenter';
34
import { MeasurementUnit } from './measurement';
@@ -82,6 +83,11 @@ export interface Transaction extends TransactionContext, Span {
8283
*/
8384
setName(name: string, source?: TransactionMetadata['source']): void;
8485

86+
/**
87+
* Set the context of a transaction event
88+
*/
89+
setContext(key: string, context: Context): void;
90+
8591
/**
8692
* Set observed measurement for this transaction.
8793
*

0 commit comments

Comments
 (0)