Skip to content

Commit 449e3d7

Browse files
committed
ref(serverless): Use startSpanManual() instead of startTransaction()
1 parent f56219a commit 449e3d7

File tree

7 files changed

+275
-406
lines changed

7 files changed

+275
-406
lines changed

packages/serverless/src/awslambda.ts

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@ import { types } from 'util';
55
/* eslint-disable max-lines */
66
import type { Scope } from '@sentry/node';
77
import * as Sentry from '@sentry/node';
8-
import { captureException, captureMessage, flush, getCurrentHub, withScope } from '@sentry/node';
9-
import type { Integration, SdkMetadata } from '@sentry/types';
10-
import { isString, logger, tracingContextFromHeaders } from '@sentry/utils';
8+
import {
9+
captureException,
10+
captureMessage,
11+
continueTrace,
12+
flush,
13+
getCurrentScope,
14+
startSpanManual,
15+
withScope,
16+
} from '@sentry/node';
17+
import type { Integration, SdkMetadata, Span } from '@sentry/types';
18+
import { isString, logger } from '@sentry/utils';
1119
// NOTE: I have no idea how to fix this right now, and don't want to waste more time, as it builds just fine — Kamil
1220
import type { Context, Handler } from 'aws-lambda';
1321
import { performance } from 'perf_hooks';
@@ -290,44 +298,13 @@ export function wrapHandler<TEvent, TResult>(
290298
}, timeoutWarningDelay) as unknown as NodeJS.Timeout;
291299
}
292300

293-
const hub = getCurrentHub();
301+
async function processResult(span?: Span): Promise<TResult> {
302+
const scope = getCurrentScope();
294303

295-
let transaction: Sentry.Transaction | undefined;
296-
if (options.startTrace) {
297-
const eventWithHeaders = event as { headers?: { [key: string]: string } };
298-
299-
const sentryTrace =
300-
eventWithHeaders.headers && isString(eventWithHeaders.headers['sentry-trace'])
301-
? eventWithHeaders.headers['sentry-trace']
302-
: undefined;
303-
const baggage = eventWithHeaders.headers?.baggage;
304-
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
305-
sentryTrace,
306-
baggage,
307-
);
308-
Sentry.getCurrentScope().setPropagationContext(propagationContext);
309-
310-
transaction = hub.startTransaction({
311-
name: context.functionName,
312-
op: 'function.aws.lambda',
313-
origin: 'auto.function.serverless',
314-
...traceparentData,
315-
metadata: {
316-
dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
317-
source: 'component',
318-
},
319-
});
320-
}
321-
322-
return withScope(async scope => {
323304
let rv: TResult;
324305
try {
325306
enhanceScopeWithEnvironmentData(scope, context, START_TIME);
326-
if (options.startTrace) {
327-
enhanceScopeWithTransactionData(scope, context);
328-
// We put the transaction on the scope so users can attach children to it
329-
scope.setSpan(transaction);
330-
}
307+
331308
rv = await asyncHandler(event, context);
332309

333310
// We manage lambdas that use Promise.allSettled by capturing the errors of failed promises
@@ -342,12 +319,46 @@ export function wrapHandler<TEvent, TResult>(
342319
throw e;
343320
} finally {
344321
clearTimeout(timeoutWarningTimer);
345-
transaction?.end();
322+
span?.end();
346323
await flush(options.flushTimeout).catch(e => {
347324
DEBUG_BUILD && logger.error(e);
348325
});
349326
}
350327
return rv;
328+
}
329+
330+
if (options.startTrace) {
331+
const eventWithHeaders = event as { headers?: { [key: string]: string } };
332+
333+
const sentryTrace =
334+
eventWithHeaders.headers && isString(eventWithHeaders.headers['sentry-trace'])
335+
? eventWithHeaders.headers['sentry-trace']
336+
: undefined;
337+
const baggage = eventWithHeaders.headers?.baggage;
338+
339+
const continueTraceContext = continueTrace({ sentryTrace, baggage });
340+
341+
return startSpanManual(
342+
{
343+
name: context.functionName,
344+
op: 'function.aws.lambda',
345+
origin: 'auto.function.serverless',
346+
...continueTraceContext,
347+
metadata: {
348+
...continueTraceContext.metadata,
349+
source: 'component',
350+
},
351+
},
352+
span => {
353+
enhanceScopeWithTransactionData(getCurrentScope(), context);
354+
355+
return processResult(span);
356+
},
357+
);
358+
}
359+
360+
return withScope(async () => {
361+
return processResult(undefined);
351362
});
352363
};
353364
}
Lines changed: 48 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { captureException, flush, getCurrentHub, getCurrentScope } from '@sentry/node';
1+
import { captureException, flush, getCurrentScope, startSpanManual } from '@sentry/node';
22
import { isThenable, logger } from '@sentry/utils';
33

44
import { DEBUG_BUILD } from '../debug-build';
@@ -21,7 +21,6 @@ export function wrapCloudEventFunction(
2121
return proxyFunction(fn, f => domainify(_wrapCloudEventFunction(f, wrapOptions)));
2222
}
2323

24-
/** */
2524
function _wrapCloudEventFunction(
2625
fn: CloudEventFunction | CloudEventFunctionWithCallback,
2726
wrapOptions: Partial<CloudEventFunctionWrapperOptions> = {},
@@ -31,63 +30,59 @@ function _wrapCloudEventFunction(
3130
...wrapOptions,
3231
};
3332
return (context, callback) => {
34-
const hub = getCurrentHub();
33+
return startSpanManual(
34+
{
35+
name: context.type || '<unknown>',
36+
op: 'function.gcp.cloud_event',
37+
origin: 'auto.function.serverless.gcp_cloud_event',
38+
metadata: { source: 'component' },
39+
},
40+
span => {
41+
const scope = getCurrentScope();
42+
scope.setContext('gcp.function.context', { ...context });
3543

36-
const transaction = hub.startTransaction({
37-
name: context.type || '<unknown>',
38-
op: 'function.gcp.cloud_event',
39-
origin: 'auto.function.serverless.gcp_cloud_event',
40-
metadata: { source: 'component' },
41-
}) as ReturnType<typeof hub.startTransaction> | undefined;
44+
const newCallback = domainify((...args: unknown[]) => {
45+
if (args[0] !== null && args[0] !== undefined) {
46+
captureException(args[0], scope => markEventUnhandled(scope));
47+
}
48+
span?.end();
4249

43-
// getCurrentHub() is expected to use current active domain as a carrier
44-
// since functions-framework creates a domain for each incoming request.
45-
// So adding of event processors every time should not lead to memory bloat.
46-
const scope = getCurrentScope();
47-
scope.setContext('gcp.function.context', { ...context });
48-
// We put the transaction on the scope so users can attach children to it
49-
scope.setSpan(transaction);
50-
51-
const newCallback = domainify((...args: unknown[]) => {
52-
if (args[0] !== null && args[0] !== undefined) {
53-
captureException(args[0], scope => markEventUnhandled(scope));
54-
}
55-
transaction?.end();
56-
57-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
58-
flush(options.flushTimeout)
59-
.then(null, e => {
60-
DEBUG_BUILD && logger.error(e);
61-
})
62-
.then(() => {
63-
callback(...args);
50+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
51+
flush(options.flushTimeout)
52+
.then(null, e => {
53+
DEBUG_BUILD && logger.error(e);
54+
})
55+
.then(() => {
56+
callback(...args);
57+
});
6458
});
65-
});
6659

67-
if (fn.length > 1) {
68-
let fnResult;
69-
try {
70-
fnResult = (fn as CloudEventFunctionWithCallback)(context, newCallback);
71-
} catch (err) {
72-
captureException(err, scope => markEventUnhandled(scope));
73-
throw err;
74-
}
60+
if (fn.length > 1) {
61+
let fnResult;
62+
try {
63+
fnResult = (fn as CloudEventFunctionWithCallback)(context, newCallback);
64+
} catch (err) {
65+
captureException(err, scope => markEventUnhandled(scope));
66+
throw err;
67+
}
7568

76-
if (isThenable(fnResult)) {
77-
fnResult.then(null, err => {
78-
captureException(err, scope => markEventUnhandled(scope));
79-
throw err;
80-
});
81-
}
69+
if (isThenable(fnResult)) {
70+
fnResult.then(null, err => {
71+
captureException(err, scope => markEventUnhandled(scope));
72+
throw err;
73+
});
74+
}
8275

83-
return fnResult;
84-
}
76+
return fnResult;
77+
}
8578

86-
return Promise.resolve()
87-
.then(() => (fn as CloudEventFunction)(context))
88-
.then(
89-
result => newCallback(null, result),
90-
err => newCallback(err, undefined),
91-
);
79+
return Promise.resolve()
80+
.then(() => (fn as CloudEventFunction)(context))
81+
.then(
82+
result => newCallback(null, result),
83+
err => newCallback(err, undefined),
84+
);
85+
},
86+
);
9287
};
9388
}
Lines changed: 50 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { captureException, flush, getCurrentHub, getCurrentScope } from '@sentry/node';
1+
import { captureException, flush, getCurrentScope, startSpanManual } from '@sentry/node';
22
import { isThenable, logger } from '@sentry/utils';
33

44
import { DEBUG_BUILD } from '../debug-build';
@@ -33,65 +33,61 @@ function _wrapEventFunction<F extends EventFunction | EventFunctionWithCallback>
3333
return (...eventFunctionArguments: Parameters<F>): ReturnType<F> | Promise<void> => {
3434
const [data, context, callback] = eventFunctionArguments;
3535

36-
const hub = getCurrentHub();
36+
return startSpanManual(
37+
{
38+
name: context.eventType,
39+
op: 'function.gcp.event',
40+
origin: 'auto.function.serverless.gcp_event',
41+
metadata: { source: 'component' },
42+
},
43+
span => {
44+
const scope = getCurrentScope();
45+
scope.setContext('gcp.function.context', { ...context });
3746

38-
const transaction = hub.startTransaction({
39-
name: context.eventType,
40-
op: 'function.gcp.event',
41-
origin: 'auto.function.serverless.gcp_event',
42-
metadata: { source: 'component' },
43-
}) as ReturnType<typeof hub.startTransaction> | undefined;
44-
45-
// getCurrentHub() is expected to use current active domain as a carrier
46-
// since functions-framework creates a domain for each incoming request.
47-
// So adding of event processors every time should not lead to memory bloat.
48-
const scope = getCurrentScope();
49-
scope.setContext('gcp.function.context', { ...context });
50-
// We put the transaction on the scope so users can attach children to it
51-
scope.setSpan(transaction);
52-
53-
const newCallback = domainify((...args: unknown[]) => {
54-
if (args[0] !== null && args[0] !== undefined) {
55-
captureException(args[0], scope => markEventUnhandled(scope));
56-
}
57-
transaction?.end();
58-
59-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
60-
flush(options.flushTimeout)
61-
.then(null, e => {
62-
DEBUG_BUILD && logger.error(e);
63-
})
64-
.then(() => {
65-
if (typeof callback === 'function') {
66-
callback(...args);
47+
const newCallback = domainify((...args: unknown[]) => {
48+
if (args[0] !== null && args[0] !== undefined) {
49+
captureException(args[0], scope => markEventUnhandled(scope));
6750
}
51+
span?.end();
52+
53+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
54+
flush(options.flushTimeout)
55+
.then(null, e => {
56+
DEBUG_BUILD && logger.error(e);
57+
})
58+
.then(() => {
59+
if (typeof callback === 'function') {
60+
callback(...args);
61+
}
62+
});
6863
});
69-
});
7064

71-
if (fn.length > 2) {
72-
let fnResult;
73-
try {
74-
fnResult = (fn as EventFunctionWithCallback)(data, context, newCallback);
75-
} catch (err) {
76-
captureException(err, scope => markEventUnhandled(scope));
77-
throw err;
78-
}
65+
if (fn.length > 2) {
66+
let fnResult;
67+
try {
68+
fnResult = (fn as EventFunctionWithCallback)(data, context, newCallback);
69+
} catch (err) {
70+
captureException(err, scope => markEventUnhandled(scope));
71+
throw err;
72+
}
7973

80-
if (isThenable(fnResult)) {
81-
fnResult.then(null, err => {
82-
captureException(err, scope => markEventUnhandled(scope));
83-
throw err;
84-
});
85-
}
74+
if (isThenable(fnResult)) {
75+
fnResult.then(null, err => {
76+
captureException(err, scope => markEventUnhandled(scope));
77+
throw err;
78+
});
79+
}
8680

87-
return fnResult;
88-
}
81+
return fnResult;
82+
}
8983

90-
return Promise.resolve()
91-
.then(() => (fn as EventFunction)(data, context))
92-
.then(
93-
result => newCallback(null, result),
94-
err => newCallback(err, undefined),
95-
);
84+
return Promise.resolve()
85+
.then(() => (fn as EventFunction)(data, context))
86+
.then(
87+
result => newCallback(null, result),
88+
err => newCallback(err, undefined),
89+
);
90+
},
91+
);
9692
};
9793
}

0 commit comments

Comments
 (0)