8000 Set global event processor and pass scope data for transactions in se… · rchl/sentry-javascript@2ed3b46 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2ed3b46

Browse files
authored
Set global event processor and pass scope data for transactions in serverless (getsentry#2975)
1 parent 83c8e45 commit 2ed3b46

File tree

10 files changed

+371
-335
lines changed

10 files changed

+371
-335
lines changed

packages/serverless/src/awslambda.ts

Lines changed: 10 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ import {
44
flush,
55
getCurrentHub,
66
Scope,
7-
SDK_VERSION,
87
Severity,
98
startTransaction,
109
withScope,
1110
} from '@sentry/node';
1211
import * as Sentry from '@sentry/node';
1312
import { Integration } from '@sentry/types';
14-
import { addExceptionMechanism } from '@sentry/utils';
1513
// 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
1614
// eslint-disable-next-line import/no-unresolved
1715
import { Context, Handler } from 'aws-lambda';
@@ -20,6 +18,7 @@ import { performance } from 'perf_hooks';
2018
import { types } from 'util';
2119

2220
import { AWSServices } from './awsservices';
21+
import { serverlessEventProcessor } from './utils';
2322

2423
export * from '@sentry/node';
2524

@@ -54,37 +53,8 @@ export function init(options: Sentry.NodeOptions = {}): void {
5453
if (options.defaultIntegrations === undefined) {
5554
options.defaultIntegrations = defaultIntegrations;
5655
}
57-
return Sentry.init(options);
58-
}
59-
60-
/**
61-
* Add event processor that will override SDK details to point to the serverless SDK instead of Node,
62-
* as well as set correct mechanism type, which should be set to `handled: false`.
63-
* We do it like this, so that we don't introduce any side-effects in this module, which makes it tree-shakeable.
64-
* @param scope Scope that processor should be added to
65-
*/
66-
function addServerlessEventProcessor(scope: Scope): void {
67-
scope.addEventProcessor(event => {
68-
event.sdk = {
69-
...event.sdk,
70-
name: 'sentry.javascript.serverless',
71-
integrations: [...((event.sdk && event.sdk.integrations) || []), 'AWSLambda'],
72-
packages: [
73-
...((event.sdk && event.sdk.packages) || []),
74-
{
75-
name: 'npm:@sentry/serverless',
76-
version: SDK_VERSION,
77-
},
78-
],
79-
version: SDK_VERSION,
80-
};
81-
82-
addExceptionMechanism(event, {
83-
handled: false,
84-
});
85-
86-
return event;
87-
});
56+
Sentry.init(options);
57+
Sentry.addGlobalEventProcessor(serverlessEventProcessor('AWSLambda'));
8858
}
8959

9060
/**
@@ -125,20 +95,6 @@ function enhanceScopeWithEnvironmentData(scope: Scope, context: Context): void {
12595
});
12696
}
12797

128-
/**
129-
* Capture exception with a a context.
130-
*
131-
* @param e exception to be captured
132-
* @param context Context
133-
*/
134-
function captureExceptionWithContext(e: unknown, context: Context): void {
135-
withScope(scope => {
136-
addServerlessEventProcessor(scope);
137-
enhanceScopeWithEnvironmentData(scope, context);
138-
captureException(e);
139-
});
140-
}
141-
14298
/**
14399
* Wraps a lambda handler adding it error capture and tracing capabilities.
144100
*
@@ -205,8 +161,6 @@ export function wrapHandler<TEvent, TResult>(
205161

206162
timeoutWarningTimer = setTimeout(() => {
207163
withScope(scope => {
208-
addServerlessEventProcessor(scope);
209-
enhanceScopeWithEnvironmentData(scope, context);
210164
scope.setTag('timeout', humanReadableTimeout);
211165
captureMessage(`Possible function timeout: ${context.functionName}`, Severity.Warning);
212166
});
@@ -217,22 +171,24 @@ export function wrapHandler<TEvent, TResult>(
217171
name: context.functionName,
218172
op: 'awslambda.handler',
219173
});
220-
// We put the transaction on the scope so users can attach children to it
221-
getCurrentHub().configureScope(scope => {
222-
scope.setSpan(transaction);
223-
});
224174

175+
const hub = getCurrentHub();
176+
const scope = hub.pushScope();
225177
let rv: TResult | undefined;
226178
try {
179+
enhanceScopeWithEnvironmentData(scope, context);
180+
// We put the transaction on the scope so users can attach children to it
181+
scope.setSpan(transaction);
227182
rv = await asyncHandler(event, context);
228183
} catch (e) {
229-
captureExceptionWithContext(e, context);
184+
captureException(e);
230185
if (options.rethrowAfterCapture) {
231186
throw e;
232187
}
233188
} finally {
234189
clearTimeout(timeoutWarningTimer);
235190
transaction.finish();
191+
hub.popScope();
236192
await flush(options.flushTimeout);
237193
}
238194
return rv;

packages/serverless/src/gcpfunction/cloud_events.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import {
44
CloudEventFunction,
55
CloudEventFunctionWithCallback,
66
} from '@google-cloud/functions-framework/build/src/functions';
7-
import { flush, getCurrentHub, startTransaction } from '@sentry/node';
7+
import { captureException, flush, getCurrentHub, startTransaction } from '@sentry/node';
88
import { logger } from '@sentry/utils';
99

10-
import { captureEventError, getActiveDomain, WrapperOptions } from './general';
10+
import { configureScopeWithContext, getActiveDomain, WrapperOptions } from './general';
1111

1212
export type CloudEventFunctionWrapperOptions = WrapperOptions;
1313

@@ -32,20 +32,22 @@ export function wrapCloudEventFunction(
3232
op: 'gcp.function.cloud_event',
3333
});
3434

35-
// We put the transaction on the scope so users can attach children to it
35+
// getCurrentHub() is expected to use current active domain as a carrier
36+
// since functions-framework creates a domain for each incoming request.
37+
// So adding of event processors every time should not lead to memory bloat.
3638
getCurrentHub().configureScope(scope => {
39+
configureScopeWithContext(scope, context);
40+
// We put the transaction on the scope so users can attach children to it
3741
scope.setSpan(transaction);
3842
});
3943

4044
const activeDomain = getActiveDomain();
4145

42-
activeDomain.on('error', err => {
43-
captureEventError(err, context);
44-
});
46+
activeDomain.on('error', captureException);
4547

4648
const newCallback = activeDomain.bind((...args: unknown[]) => {
4749
if (args[0] !== null && args[0] !== undefined) {
48-
captureEventError(args[0], context);
50+
captureException(args[0]);
4951
}
5052
transaction.finish();
5153

packages/serverless/src/gcpfunction/events.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// '@google-cloud/functions-framework/build/src/functions' import is expected to be type-only so it's erased in the final .js file.
22
// When TypeScript compiler is upgraded, use `import type` syntax to explicitly assert that we don't want to load a module here.
33
import { EventFunction, EventFunctionWithCallback } from '@google-cloud/functions-framework/build/src/functions';
4-
import { flush, getCurrentHub, startTransaction } from '@sentry/node';
4+
import { captureException, flush, getCurrentHub, startTransaction } from '@sentry/node';
55
import { logger } from '@sentry/utils';
66

7-
import { captureEventError, getActiveDomain, WrapperOptions } from './general';
7+
import { configureScopeWithContext, getActiveDomain, WrapperOptions } from './general';
88

99
export type EventFunctionWrapperOptions = WrapperOptions;
1010

@@ -29,20 +29,22 @@ export function wrapEventFunction(
2929
op: 'gcp.function.event',
3030
});
3131

32-
// We put the transaction on the scope so users can attach children to it
32+
// getCurrentHub() is expected to use current active domain as a carrier
33+
// since functions-framework creates a domain for each incoming request.
34+
// So adding of event processors every time should not lead to memory bloat.
3335
getCurrentHub().configureScope(scope => {
36+
configureScopeWithContext(scope, context);
37+
// We put the transaction on the scope so users can attach children to it
3438
scope.setSpan(transaction);
3539
});
3640

3741
const activeDomain = getActiveDomain();
3842

39-
activeDomain.on('error', err => {
40-
captureEventError(err, context);
41-
});
43+
activeDomain.on('error', captureException);
4244

4345
const newCallback = activeDomain.bind((...args: unknown[]) => {
4446
if (args[0] !== null && args[0] !== undefined) {
45-
captureEventError(args[0], context);
47+
captureException(args[0]);
4648
}
4749
transaction.finish();
4850

packages/serverless/src/gcpfunction/general.ts

Lines changed: 9 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
// '@google-cloud/functions-framework/build/src/functions' import is expected to be type-only so it's erased in the final .js file.
22
// When TypeScript compiler is upgraded, use `import type` syntax to explicitly assert that we don't want to load a module here.
33
import { Context } from '@google-cloud/functions-framework/build/src/functions';
4-
import { captureException, Scope, SDK_VERSION, withScope } from '@sentry/node';
4+
import { Scope } from '@sentry/node';
55
import { Context as SentryContext } from '@sentry/types';
6-
import { addExceptionMechanism } from '@sentry/utils';
76
import * as domain from 'domain';
87
import { hostname } from 'os';
98

@@ -12,52 +11,18 @@ export interface WrapperOptions {
1211
}
1312

1413
/**
15-
* Capture exception with additional event information.
14+
* Enhances the scope with additional event information.
1615
*
17-
* @param e exception to be captured
16+
* @param scope scope
1817
* @param context event context
1918
*/
20-
export function captureEventError(e: unknown, context: Context): void {
21-
withScope(scope => {
22-
addServerlessEventProcessor(scope);
23-
scope.setContext('runtime', {
24-
name: 'node',
25-
version: global.process.version,
26-
});
27-
scope.setTag('server_name', process.env.SENTRY_NAME || hostname());
28-
scope.setContext('gcp.function.context', { ...context } as SentryContext);
29-
captureException(e);
30-
});
31-
}
32-
33-
/**
34-
* Add event processor that will override SDK details to point to the serverless SDK instead of Node,
35-
* as well as set correct mechanism type, which should be set to `handled: false`.
36-
* We do it like this, so that we don't introduce any side-effects in this module, which makes it tree-shakeable.
37-
* @param scope Scope that processor should be added to
38-
*/
39-
export function addServerlessEventProcessor(scope: Scope): void {
40-
scope.addEventProcessor(event => {
41-
event.sdk = {
42-
...event.sdk,
43-
name: 'sentry.javascript.serverless',
44-
integrations: [...((event.sdk && event.sdk.integrations) || []), 'GCPFunction'],
45-
packages: [
46-
...((event.sdk && event.sdk.packages) || []),
47-
{
48-
name: 'npm:@sentry/serverless',
49-
version: SDK_VERSION,
50-
},
51-
],
52-
version: SDK_VERSION,
53-
};
54-
55-
addExceptionMechanism(event, {
56-
handled: false,
57-
});
58-
59-
return event;
19+
export function configureScopeWithContext(scope: Scope, context: Context): void {
20+
scope.setContext('runtime', {
21+
name: 'node',
22+
version: global.process.version,
6023
});
24+
scope.setTag('server_name', process.env.SENTRY_NAME || hostname());
25+
scope.setContext('gcp.function.context', { ...context } as SentryContext);
6126
}
6227

6328
/**

packages/serverless/src/gcpfunction/http.ts

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// '@google-cloud/functions-framework/build/src/functions' import is expected to be type-only so it's erased in the final .js file.
22
// When TypeScript compiler is upgraded, use `import type` syntax to explicitly assert that we don't want to load a module here.
33
import { HttpFunction } from '@google-cloud/functions-framework/build/src/functions';
4-
import { captureException, flush, getCurrentHub, Handlers, startTransaction, withScope } from '@sentry/node';
4+
import { captureException, flush, getCurr 10000 entHub, Handlers, startTransaction } from '@sentry/node';
55
import { logger, stripUrlQueryAndFragment } from '@sentry/utils';
66

7-
import { addServerlessEventProcessor, getActiveDomain, WrapperOptions } from './general';
7+
import { getActiveDomain, WrapperOptions } from './general';
88

99
type Request = Parameters<HttpFunction>[0];
1010
type Response = Parameters<HttpFunction>[1];
@@ -18,21 +18,6 @@ export { Request, Response };
1818

1919
const { parseRequest } = Handlers;
2020

21-
/**
22-
* Capture exception with additional request information.
23-
*
24-
* @param e exception to be captured
25-
* @param req incoming request
26-
* @param options request capture options
27-
*/
28-
function captureRequestError(e: unknown, req: Request, options: ParseRequestOptions): void {
29-
withScope(scope => {
30-
addServerlessEventProcessor(scope);
31-
scope.addEventProcessor(event => parseRequest(event, req, options));
32-
captureException(e);
33-
});
34-
}
35-
3621
/**
3722
* Wraps an HTTP function handler adding it error capture and tracing capabilities.
3823
*
@@ -58,8 +43,12 @@ export function wrapHttpFunction(
5843
op: 'gcp.function.http',
5944
});
6045

61-
// We put the transaction on the scope so users can attach children to it
46+
// getCurrentHub() is expected to use current active domain as a carrier
47+
// since functions-framework creates a domain for each incoming request.
48+
// So adding of event processors every time should not lead to memory bloat.
6249
getCurrentHub().configureScope(scope => {
50+
scope.addEventProcessor(event => parseRequest(event, req, options.parseRequestOptions));
51+
// We put the transaction on the scope so users can attach children to it
6352
scope.setSpan(transaction);
6453
});
6554

@@ -71,7 +60,7 @@ export function wrapHttpFunction(
7160
// functions-framework creates a domain for each incoming request so we take advantage of this fact and add an error handler.
7261
// BTW this is the only way to catch any exception occured during request lifecycle.
7362
getActiveDomain().on('error', err => {
74-
captureRequestError(err, req, options.parseRequestOptions);
63+
captureException(err);
7564
});
7665

7766
// eslint-disable-next-line @typescript-eslint/unbound-method
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1+
import * as Sentry from '@sentry/node';
2+
3+
import { serverlessEventProcessor } from '../utils';
4+
15
export * from './http';
26
export * from './events';
37
export * from './cloud_events';
4-
export { init } from '@sentry/node';
8+
9+
/**
10+
* @see {@link Sentry.init}
11+
*/
12+
export function init(options: Sentry.NodeOptions = {}): void {
13+
Sentry.init(options);
14+
Sentry.addGlobalEventProcessor(serverlessEventProcessor('GCPFunction'));
15+
}

packages/serverless/src/utils.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Event, SDK_VERSION } from '@sentry/node';
2+
import { addExceptionMechanism } from '@sentry/utils';
3+
4+
/**
5+
* Event processor that will override SDK details to point to the serverless SDK instead of Node,
6+
* as well as set correct mechanism type, which should be set to `handled: false`.
7+
* We do it like this, so that we don't introduce any side-effects in this module, which makes it tree-shakeable.
8+
* @param event Event
9+
* @param integration Name of the serverless integration ('AWSLambda', 'GCPFunction', etc)
10+
*/
11+
export function serverlessEventProcessor(integration: string): (event: Event) => Event {
12+
return event => {
13+
event.sdk = {
14+
...event.sdk,
15+
name: 'sentry.javascript.serverless',
16+
integrations: [...((event.sdk && event.sdk.integrations) || []), integration],
17+
packages: [
18+
...((event.sdk && event.sdk.packages) || []),
19+
{
20+
name: 'npm:@sentry/serverless',
21+
version: SDK_VERSION,
22+
},
23+
],
24+
version: SDK_VERSION,
25+
};
26+
27+
addExceptionMechanism(event, {
28+
handled: false,
29+
});
30+
31+
return event;
32+
};
33+
}

0 commit comments

Comments
 (0)
0