Skip to content

Commit 44deaaf

Browse files
authored
fix(serverless): Handle incoming "sentry-trace" header (#3261)
* Applying sentry-trace in AWSLambdaIntegration transaction * Propagate trace in AWSLambda functions * Propagate trace handlers in GCP http functions * Linting fix * Linting fixes * Removed redundant type
1 parent 801bfed commit 44deaaf

File tree

5 files changed

+53
-11
lines changed

5 files changed

+53
-11
lines changed

packages/serverless/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"dependencies": {
1919
"@sentry/minimal": "6.1.0",
2020
"@sentry/node": "6.1.0",
21+
"@sentry/tracing": "6.1.0",
2122
"@sentry/types": "6.1.0",
2223
"@sentry/utils": "6.1.0",
2324
"@types/aws-lambda": "^8.10.62",

packages/serverless/src/awslambda.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import {
99
withScope,
1010
} from '@sentry/node';
1111
import * as Sentry from '@sentry/node';
12+
import { extractTraceparentData } from '@sentry/tracing';
1213
import { Integration } from '@sentry/types';
13-
import { logger } from '@sentry/utils';
14+
import { isString, logger } from '@sentry/utils';
1415
// 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
1516
// eslint-disable-next-line import/no-unresolved
1617
import { Context, Handler } from 'aws-lambda';
@@ -242,9 +243,16 @@ export function wrapHandler<TEvent, TResult>(
242243
}, timeoutWarningDelay);
243244
}
244245

246+
// Applying `sentry-trace` to context
247+
let traceparentData;
248+
const eventWithHeaders = event as { headers?: { [key: string]: string } };
249+
if (eventWithHeaders.headers && isString(eventWithHeaders.headers['sentry-trace'])) {
250+
traceparentData = extractTraceparentData(eventWithHeaders.headers['sentry-trace'] as string);
251+
}
245252
const transaction = startTransaction({
246253
name: context.functionName,
247254
op: 'awslambda.handler',
255+
...traceparentData,
248256
});
249257

250258
const hub = getCurrentHub();

packages/serverless/src/gcpfunction/http.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { captureException, flush, getCurrentHub, Handlers, startTransaction } from '@sentry/node';
2-
import { logger, stripUrlQueryAndFragment } from '@sentry/utils';
2+
import { extractTraceparentData } from '@sentry/tracing';
3+
import { isString, logger, stripUrlQueryAndFragment } from '@sentry/utils';
34

45
import { domainify, getActiveDomain, proxyFunction } from './../utils';
56
import { HttpFunction, WrapperOptions } from './general';
@@ -48,9 +49,16 @@ function _wrapHttpFunction(fn: HttpFunction, wrapOptions: Partial<HttpFunctionWr
4849
const reqMethod = (req.method || '').toUpperCase();
4950
const reqUrl = stripUrlQueryAndFragment(req.originalUrl || req.url || '');
5051

52+
// Applying `sentry-trace` to context
53+
let traceparentData;
54+
const reqWithHeaders = req as { headers?: { [key: string]: string } };
55+
if (reqWithHeaders.headers && isString(reqWithHeaders.headers['sentry-trace'])) {
56+
traceparentData = extractTraceparentData(reqWithHeaders.headers['sentry-trace'] as string);
57+
}
5158
const transaction = startTransaction({
5259
name: `${reqMethod} ${reqUrl}`,
5360
op: 'gcp.function.http',
61+
...traceparentData,
5462
});
5563

5664
// getCurrentHub() is expected to use current active domain as a carrier

packages/serverless/test/awslambda.test.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Event } from '@sentry/types';
21
// 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
32
// eslint-disable-next-line import/no-unresolved
43
import { Callback, Handler } from 'aws-lambda';
@@ -16,9 +15,7 @@ const { wrapHandler } = Sentry.AWSLambda;
1615

1716
// Default `timeoutWarningLimit` is 500ms so leaving some space for it to trigger when necessary
1817
const DEFAULT_EXECUTION_TIME = 100;
19-
const fakeEvent = {
20-
fortySix: 'o_O',
21-
};
18+
let fakeEvent: { [key: string]: unknown };
2219
const fakeContext = {
2320
callbackWaitsForEmptyEventLoop: false,
2421
functionName: 'functionName',
@@ -72,6 +69,12 @@ function expectScopeSettings() {
7269
}
7370

7471
describe('AWSLambda', () => {
72+
beforeEach(() => {
73+
fakeEvent = {
74+
fortySix: 'o_O',
75+
};
76+
});
77+
7578
afterEach(() => {
7679
// @ts-ignore see "Why @ts-ignore" note
7780
Sentry.resetMocks();
@@ -228,9 +231,16 @@ describe('AWSLambda', () => {
228231
const wrappedHandler = wrapHandler(handler);
229232

230233
try {
234+
fakeEvent.headers = { 'sentry-trace': '12312012123120121231201212312012-1121201211212012-0' };
231235
await wrappedHandler(fakeEvent, fakeContext, fakeCallback);
232236
} catch (e) {
233-
expect(Sentry.startTransaction).toBeCalledWith({ name: 'functionName', op: 'awslambda.handler' });
237+
expect(Sentry.startTransaction).toBeCalledWith({
238+
name: 'functionName',
239+
op: 'awslambda.handler',
240+
traceId: '12312012123120121231201212312012',
241+
parentSpanId: '1121201211212012',
242+
parentSampled: false,
243+
});
234244
expectScopeSettings();
235245
expect(Sentry.captureException).toBeCalledWith(e);
236246
// @ts-ignore see "Why @ts-ignore" note

packages/serverless/test/gcpfunction.test.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,17 @@ describe('GCPFunction', () => {
2626
Sentry.resetMocks();
2727
});
2828

29-
async function handleHttp(fn: HttpFunction): Promise<void> {
29+
async function handleHttp(fn: HttpFunction, trace_headers: { [key: string]: string } | null = null): Promise<void> {
30+
let headers: { [key: string]: string } = { host: 'hostname', 'content-type': 'application/json' };
31+
if (trace_headers) {
32+
headers = { ...headers, ...trace_headers };
33+
}
3034
return new Promise((resolve, _reject) => {
3135
const d = domain.create();
3236
const req = {
3337
method: 'POST',
3438
url: '/path?q=query',
35-
headers: { host: 'hostname', 'content-type': 'application/json' },
39+
headers: headers,
3640
body: { foo: 'bar' },
3741
} as Request;
3842
const res = { end: resolve } as Response;
@@ -124,8 +128,19 @@ describe('GCPFunction', () => {
124128
throw error;
125129
};
126130
const wrappedHandler = wrapHttpFunction(handler);
127-
await handleHttp(wrappedHandler);
128-
expect(Sentry.startTransaction).toBeCalledWith({ name: 'POST /path', op: 'gcp.function.http' });
131+
132+
const trace_headers: { [key: string]: string } = {
133+
'sentry-trace': '12312012123120121231201212312012-1121201211212012-0',
134+
};
135+
136+
await handleHttp(wrappedHandler, trace_headers);
137+
expect(Sentry.startTransaction).toBeCalledWith({
138+
name: 'POST /path',
139+
op: 'gcp.function.http',
140+
traceId: '12312012123120121231201212312012',
141+
parentSpanId: '1121201211212012',
142+
parentSampled: false,
143+
});
129144
// @ts-ignore see "Why @ts-ignore" note
130145
expect(Sentry.fakeScope.setSpan).toBeCalledWith(Sentry.fakeTransaction);
131146
expect(Sentry.captureException).toBeCalledWith(error);

0 commit comments

Comments
 (0)