Skip to content

Commit 8b4ab49

Browse files
authored
feat(tracing): Record transaction name source when name set directly (#5396)
This adds transaction name source to transaction metadata in various situations in which the name is set directly: starting a manual transaction, assigning to the `name` property of a transaction, calling a transaction's `setName` method, and changing the name using `beforeNavigate`. In order to distinguish manually-created transactions from automatically-created ones, all internal uses of `startTransaction` have been changed so that they are happening directly on the hub, rather than through the top-level `Sentry.startTransaction()` wrapper.
1 parent aeac69d commit 8b4ab49

File tree

14 files changed

+367
-116
lines changed

14 files changed

+367
-116
lines changed

packages/hub/src/exports.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ export function withScope(callback: (scope: Scope) => void): ReturnType<Hub['wit
168168
* The transaction must be finished with a call to its `.finish()` method, at which point the transaction with all its
169169
* finished child spans will be sent to Sentry.
170170
*
171+
* NOTE: This function should only be used for *manual* instrumentation. Auto-instrumentation should call
172+
* `startTransaction` directly on the hub.
173+
*
171174
* @param context Properties of the new `Transaction`.
172175
* @param customSamplingContext Information given to the transaction sampling function (along with context-dependent
173176
* default values). See {@link Options.tracesSampler}.
@@ -178,5 +181,11 @@ export function startTransaction(
178181
context: TransactionContext,
179182
customSamplingContext?: CustomSamplingContext,
180183
): ReturnType<Hub['startTransaction']> {
181-
return getCurrentHub().startTransaction({ ...context }, customSamplingContext);
184+
return getCurrentHub().startTransaction(
185+
{
186+
metadata: { source: 'custom' },
187+
...context,
188+
},
189+
customSamplingContext,
190+
);
182191
}

packages/hub/test/exports.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
setTag,
1111
setTags,
1212
setUser,
13+
startTransaction,
1314
withScope,
1415
} from '../src/exports';
1516

@@ -184,6 +185,35 @@ describe('Top Level API', () => {
184185
});
185186
});
186187

188+
describe('startTransaction', () => {
189+
beforeEach(() => {
190+
global.__SENTRY__ = {
191+
hub: undefined,
192+
extensions: {
193+
startTransaction: (context: any) => ({
194+
name: context.name,
195+
// Spread rather than assign in case it's undefined
196+
metadata: { ...context.metadata },
197+
}),
198+
},
199+
};
200+
});
201+
202+
it("sets source to `'custom'` if no source provided", () => {
203+
const transaction = startTransaction({ name: 'dogpark' });
204+
205+
expect(transaction.name).toEqual('dogpark');
206+
expect(transaction.metadata.source).toEqual('custom');
207+
});
208+
209+
it('uses given `source` value', () => {
210+
const transaction = startTransaction({ name: 'dogpark', metadata: { source: 'route' } });
211+
212+
expect(transaction.name).toEqual('dogpark');
213+
expect(transaction.metadata.source).toEqual('route');
214+
});
215+
});
216+
187217
test('Clear Scope', () => {
188218
const client: any = new TestClient({});
189219
getCurrentHub().withScope(() => {

packages/remix/src/utils/instrumentServer.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { captureException, getCurrentHub, startTransaction } from '@sentry/node';
1+
import { captureException, getCurrentHub } from '@sentry/node';
22
import { getActiveTransaction } from '@sentry/tracing';
33
import { addExceptionMechanism, fill, loadModule, logger, stripUrlQueryAndFragment } from '@sentry/utils';
44

@@ -175,8 +175,9 @@ function makeWrappedLoader(origAction: DataFunction): DataFunction {
175175

176176
function wrapRequestHandler(origRequestHandler: RequestHandler): RequestHandler {
177177
return async function (this: unknown, request: Request, loadContext?: unknown): Promise<Response> {
178-
const currentScope = getCurrentHub().getScope();
179-
const transaction = startTransaction({
178+
const hub = getCurrentHub();
179+
const currentScope = hub.getScope();
180+
const transaction = hub.startTransaction({
180181
name: stripUrlQueryAndFragment(request.url),
181182
op: 'http.server',
182183
tags: {

packages/serverless/src/awslambda.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
/* eslint-disable max-lines */
22
import * as Sentry from '@sentry/node';
3-
import {
4-
captureException,
5-
captureMessage,
6-
flush,
7-
getCurrentHub,
8-
Scope,
9-
startTransaction,
10-
withScope,
11-
} from '@sentry/node';
3+
import { captureException, captureMessage, flush, getCurrentHub, Scope, withScope } from '@sentry/node';
124
import { extractTraceparentData } from '@sentry/tracing';
135
import { Integration } from '@sentry/types';
146
import { dsnFromString, dsnToString, isString, logger, parseBaggageSetMutability } from '@sentry/utils';
@@ -320,14 +312,15 @@ export function wrapHandler<TEvent, TResult>(
320312
eventWithHeaders.headers && isString(eventWithHeaders.headers.baggage) && eventWithHeaders.headers.baggage;
321313
const baggage = parseBaggageSetMutability(rawBaggageString, traceparentData);
322314

323-
const transaction = startTransaction({
315+
const hub = getCurrentHub();
316+
317+
const transaction = hub.startTransaction({
324318
name: context.functionName,
325319
op: 'awslambda.handler',
326320
...traceparentData,
327321
metadata: { baggage, source: 'component' },
328322
});
329323

330-
const hub = getCurrentHub();
331324
const scope = hub.pushScope();
332325
let rv: TResult;
333326
try {

packages/serverless/src/gcpfunction/cloud_events.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { captureException, flush, getCurrentHub, startTransaction } from '@sentry/node';
1+
import { captureException, flush, getCurrentHub } from '@sentry/node';
22
import { logger } from '@sentry/utils';
33

44
import { domainify, getActiveDomain, proxyFunction } from '../utils';
@@ -30,7 +30,9 @@ function _wrapCloudEventFunction(
3030
...wrapOptions,
3131
};
3232
return (context, callback) => {
33-
const transaction = startTransaction({
33+
const hub = getCurrentHub();
34+
35+
const transaction = hub.startTransaction({
3436
name: context.type || '<unknown>',
3537
op: 'gcp.function.cloud_event',
3638
metadata: { source: 'component' },
@@ -39,7 +41,7 @@ function _wrapCloudEventFunction(
3941
// getCurrentHub() is expected to use current active domain as a carrier
4042
// since functions-framework creates a domain for each incoming request.
4143
// So adding of event processors every time should not lead to memory bloat.
42-
getCurrentHub().configureScope(scope => {
44+
hub.configureScope(scope => {
4345
scope.setContext('gcp.function.context', { ...context });
4446
// We put the transaction on the scope so users can attach children to it
4547
scope.setSpan(transaction);

packages/serverless/src/gcpfunction/events.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { captureException, flush, getCurrentHub, startTransaction } from '@sentry/node';
1+
import { captureException, flush, getCurrentHub } from '@sentry/node';
22
import { logger } from '@sentry/utils';
33

44
import { domainify, getActiveDomain, proxyFunction } from '../utils';
@@ -30,7 +30,9 @@ function _wrapEventFunction(
3030
...wrapOptions,
3131
};
3232
return (data, context, callback) => {
33-
const transaction = startTransaction({
33+
const hub = getCurrentHub();
34+
35+
const transaction = hub.startTransaction({
3436
name: context.eventType,
3537
op: 'gcp.function.event',
3638
metadata: { source: 'component' },
@@ -39,7 +41,7 @@ function _wrapEventFunction(
3941
// getCurrentHub() is expected to use current active domain as a carrier
4042
// since functions-framework creates a domain for each incoming request.
4143
// So adding of event processors every time should not lead to memory bloat.
42-
getCurrentHub().configureScope(scope => {
44+
hub.configureScope(scope => {
4345
scope.setContext('gcp.function.context', { ...context });
4446
// We put the transaction on the scope so users can attach children to it
4547
scope.setSpan(transaction);

packages/serverless/src/gcpfunction/http.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
captureException,
55
flush,
66
getCurrentHub,
7-
startTransaction,
87
} from '@sentry/node';
98
import { extractTraceparentData } from '@sentry/tracing';
109
import { isString, logger, parseBaggageSetMutability, stripUrlQueryAndFragment } from '@sentry/utils';
@@ -83,7 +82,9 @@ function _wrapHttpFunction(fn: HttpFunction, wrapOptions: Partial<HttpFunctionWr
8382

8483
const baggage = parseBaggageSetMutability(rawBaggageString, traceparentData);
8584

86-
const transaction = startTransaction({
85+
const hub = getCurrentHub();
86+
87+
const transaction = hub.startTransaction({
8788
name: `${reqMethod} ${reqUrl}`,
8889
op: 'gcp.function.http',
8990
...traceparentData,
@@ -93,7 +94,7 @@ function _wrapHttpFunction(fn: HttpFunction, wrapOptions: Partial<HttpFunctionWr
9394
// getCurrentHub() is expected to use current active domain as a carrier
9495
// since functions-framework creates a domain for each incoming request.
9596
// So adding of event processors every time should not lead to memory bloat.
96-
getCurrentHub().configureScope(scope => {
97+
hub.configureScope(scope => {
9798
scope.addEventProcessor(event => addRequestDataToEvent(event, req, options.addRequestDataToEventOptions));
9899
// We put the transaction on the scope so users can attach children to it
99100
scope.setSpan(transaction);

packages/serverless/test/__mocks__/@sentry/node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const fakeHub = {
1111
pushScope: jest.fn(() => fakeScope),
1212
popScope: jest.fn(),
1313
getScope: jest.fn(() => fakeScope),
14+
startTransaction: jest.fn(context => ({ ...fakeTransaction, ...context })),
1415
};
1516
export const fakeScope = {
1617
addEventProcessor: jest.fn(),

packages/serverless/test/awslambda.test.ts

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ const fakeCallback: Callback = (err, result) => {
3838
return err;
3939
};
4040

41-
function expectScopeSettings() {
41+
function expectScopeSettings(fakeTransactionContext: any) {
4242
// @ts-ignore see "Why @ts-ignore" note
43-
expect(Sentry.fakeScope.setSpan).toBeCalledWith(Sentry.fakeTransaction);
43+
const fakeTransaction = { ...Sentry.fakeTransaction, ...fakeTransactionContext };
44+
// @ts-ignore see "Why @ts-ignore" note
45+
expect(Sentry.fakeScope.setSpan).toBeCalledWith(fakeTransaction);
4446
// @ts-ignore see "Why @ts-ignore" note
4547
expect(Sentry.fakeScope.setTag).toBeCalledWith('server_name', expect.anything());
4648
// @ts-ignore see "Why @ts-ignore" note
@@ -186,13 +188,17 @@ describe('AWSLambda', () => {
186188
};
187189
const wrappedHandler = wrapHandler(handler);
188190
const rv = await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
189-
expect(rv).toStrictEqual(42);
190-
expect(Sentry.startTransaction).toBeCalledWith({
191+
192+
const fakeTransactionContext = {
191193
name: 'functionName',
192194
op: 'awslambda.handler',
193195
metadata: { baggage: [{}, '', true], source: 'component' },
194-
});
195-
expectScopeSettings();
196+
};
197+
198+
expect(rv).toStrictEqual(42);
199+
// @ts-ignore see "Why @ts-ignore" note
200+
expect(Sentry.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext);
201+
expectScopeSettings(fakeTransactionContext);
196202
// @ts-ignore see "Why @ts-ignore" note
197203
expect(Sentry.fakeTransaction.finish).toBeCalled();
198204
expect(Sentry.flush).toBeCalledWith(2000);
@@ -210,12 +216,15 @@ describe('AWSLambda', () => {
210216
try {
211217
await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
212218
} catch (e) {
213-
expect(Sentry.startTransaction).toBeCalledWith({
219+
const fakeTransactionContext = {
214220
name: 'functionName',
215221
op: 'awslambda.handler',
216222
metadata: { baggage: [{}, '', true], source: 'component' },
217-
});
218-
expectScopeSettings();
223+
};
224+
225+
// @ts-ignore see "Why @ts-ignore" note
226+
expect(Sentry.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext);
227+
expectScopeSettings(fakeTransactionContext);
219228
expect(Sentry.captureException).toBeCalledWith(error);
220229
// @ts-ignore see "Why @ts-ignore" note
221230
expect(Sentry.fakeTransaction.finish).toBeCalled();
@@ -244,7 +253,8 @@ describe('AWSLambda', () => {
244253
};
245254

246255
const handler: Handler = (_event, _context, callback) => {
247-
expect(Sentry.startTransaction).toBeCalledWith(
256+
// @ts-ignore see "Why @ts-ignore" note
257+
expect(Sentry.fakeHub.startTransaction).toBeCalledWith(
248258
expect.objectContaining({
249259
parentSpanId: '1121201211212012',
250260
parentSampled: false,
@@ -284,15 +294,18 @@ describe('AWSLambda', () => {
284294
fakeEvent.headers = { 'sentry-trace': '12312012123120121231201212312012-1121201211212012-0' };
285295
await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
286296
} catch (e) {
287-
expect(Sentry.startTransaction).toBeCalledWith({
297+
const fakeTransactionContext = {
288298
name: 'functionName',
289299
op: 'awslambda.handler',
290300
traceId: '12312012123120121231201212312012',
291301
parentSpanId: '1121201211212012',
292302
parentSampled: false,
293303
metadata: { baggage: [{}, '', false], source: 'component' },
294-
});
295-
expectScopeSettings();
304+
};
305+
306+
// @ts-ignore see "Why @ts-ignore" note
307+
expect(Sentry.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext);
308+
expectScopeSettings(fakeTransactionContext);
296309
expect(Sentry.captureException).toBeCalledWith(e);
297310
// @ts-ignore see "Why @ts-ignore" note
298311
expect(Sentry.fakeTransaction.finish).toBeCalled();
@@ -310,13 +323,17 @@ describe('AWSLambda', () => {
310323
};
311324
const wrappedHandler = wrapHandler(handler);
312325
const rv = await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
313-
expect(rv).toStrictEqual(42);
314-
expect(Sentry.startTransaction).toBeCalledWith({
326+
327+
const fakeTransactionContext = {
315328
name: 'functionName',
316329
op: 'awslambda.handler',
317330
metadata: { baggage: [{}, '', true], source: 'component' },
318-
});
319-
expectScopeSettings();
331+
};
332+
333+
expect(rv).toStrictEqual(42);
334+
// @ts-ignore see "Why @ts-ignore" note
335+
expect(Sentry.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext);
336+
expectScopeSettings(fakeTransactionContext);
320337
// @ts-ignore see "Why @ts-ignore" note
321338
expect(Sentry.fakeTransaction.finish).toBeCalled();
322339
expect(Sentry.flush).toBeCalled();
@@ -345,12 +362,15 @@ describe('AWSLambda', () => {
345362
try {
346363
await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
347364
} catch (e) {
348-
expect(Sentry.startTransaction).toBeCalledWith({
365+
const fakeTransactionContext = {
349366
name: 'functionName',
350367
op: 'awslambda.handler',
351368
metadata: { baggage: [{}, '', true], source: 'component' },
352-
});
353-
expectScopeSettings();
369+
};
370+
371+
// @ts-ignore see "Why @ts-ignore" note
372+
expect(Sentry.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext);
373+
expectScopeSettings(fakeTransactionContext);
354374
expect(Sentry.captureException).toBeCalledWith(error);
355375
// @ts-ignore see "Why @ts-ignore" note
356376
expect(Sentry.fakeTransaction.finish).toBeCalled();
@@ -383,13 +403,17 @@ describe('AWSLambda', () => {
383403
};
384404
const wrappedHandler = wrapHandler(handler);
385405
const rv = await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
386-
expect(rv).toStrictEqual(42);
387-
expect(Sentry.startTransaction).toBeCalledWith({
406+
407+
const fakeTransactionContext = {
388408
name: 'functionName',
389409
op: 'awslambda.handler',
390410
metadata: { baggage: [{}, '', true], source: 'component' },
391-
});
392-
expectScopeSettings();
411+
};
412+
413+
expect(rv).toStrictEqual(42);
414+
// @ts-ignore see "Why @ts-ignore" note
415+
expect(Sentry.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext);
416+
expectScopeSettings(fakeTransactionContext);
393417
// @ts-ignore see "Why @ts-ignore" note
394418
expect(Sentry.fakeTransaction.finish).toBeCalled();
395419
expect(Sentry.flush).toBeCalled();
@@ -418,12 +442,15 @@ describe('AWSLambda', () => {
418442
try {
419443
await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
420444
} catch (e) {
421-
expect(Sentry.startTransaction).toBeCalledWith({
445+
const fakeTransactionContext = {
422446
name: 'functionName',
423447
op: 'awslambda.handler',
424448
metadata: { baggage: [{}, '', true], source: 'component' },
425-
});
426-
expectScopeSettings();
449+
};
450+
451+
// @ts-ignore see "Why @ts-ignore" note
452+
expect(Sentry.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext);
453+
expectScopeSettings(fakeTransactionContext);
427454
expect(Sentry.captureException).toBeCalledWith(error);
428455
// @ts-ignore see "Why @ts-ignore" note
429456
expect(Sentry.fakeTransaction.finish).toBeCalled();

0 commit comments

Comments
 (0)