-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(aws-serverless): Extract sentry trace data from handler
context
…
… over `event` (#13266) Currently, the AWS otel integration (and our `wrapHandler` fallback) try to extract sentry trace data from the `event` object passed to a Lambda call. The aws-sdk integration, however, places tracing data onto `context.clientContext.Custom`. This PR adds a custom `eventContextExtractor` that attempts extracting sentry trace data from the `context`, with a fallback to `event` to enable distributed tracing among Lambda invocations. Traces are now connected. Here an example: `Lambda-A` calling `Lambda-B`: ``` import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda"; import * as Sentry from "@sentry/aws-serverless"; export const handler = Sentry.wrapHandler(async (event, context) => { const client = new LambdaClient(); const command = new InvokeCommand({ FunctionName: `Lambda-B`, InvocationType: "RequestResponse", Payload: new Uint16Array(), }) return client.send(command); }); ``` `Lambda-B`: ``` import * as Sentry from "@sentry/aws-serverless"; Sentry.addIntegration(Sentry.postgresIntegration()) export const handler = Sentry.wrapHandler(async (event) => { const queryString = "select count(*) from myTable;"; return await Sentry.startSpan({ name: queryString, op: "db.sql.execute" }, async (span) => { console.log('executing query', queryString); }) }) ``` ![CleanShot 2024-08-07 at 16 34 51@2x](https://github.com/user-attachments/assets/43f5dd9e-e5af-4667-9551-05fac90f03a6) Closes: #13146
- Loading branch information
1 parent
6cbc416
commit b17ac59
Showing
5 changed files
with
217 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { eventContextExtractor, getAwsTraceData } from '../src/utils'; | ||
|
||
const mockExtractContext = jest.fn(); | ||
jest.mock('@opentelemetry/api', () => { | ||
const actualApi = jest.requireActual('@opentelemetry/api'); | ||
return { | ||
...actualApi, | ||
propagation: { | ||
extract: (...args: unknown[]) => mockExtractContext(args), | ||
}, | ||
}; | ||
}); | ||
|
||
const mockContext = { | ||
clientContext: { | ||
Custom: { | ||
'sentry-trace': '12345678901234567890123456789012-1234567890123456-1', | ||
baggage: 'sentry-environment=production', | ||
}, | ||
}, | ||
}; | ||
const mockEvent = { | ||
headers: { | ||
'sentry-trace': '12345678901234567890123456789012-1234567890123456-2', | ||
baggage: 'sentry-environment=staging', | ||
}, | ||
}; | ||
|
||
describe('getTraceData', () => { | ||
test('gets sentry trace data from the context', () => { | ||
// @ts-expect-error, a partial context object is fine here | ||
const traceData = getAwsTraceData({}, mockContext); | ||
|
||
expect(traceData['sentry-trace']).toEqual('12345678901234567890123456789012-1234567890123456-1'); | ||
expect(traceData.baggage).toEqual('sentry-environment=production'); | ||
}); | ||
|
||
test('gets sentry trace data from the context even if event has data', () => { | ||
// @ts-expect-error, a partial context object is fine here | ||
const traceData = getAwsTraceData(mockEvent, mockContext); | ||
|
||
expect(traceData['sentry-trace']).toEqual('12345678901234567890123456789012-1234567890123456-1'); | ||
expect(traceData.baggage).toEqual('sentry-environment=production'); | ||
}); | ||
|
||
test('gets sentry trace data from the event if no context is passed', () => { | ||
const traceData = getAwsTraceData(mockEvent); | ||
|
||
expect(traceData['sentry-trace']).toEqual('12345678901234567890123456789012-1234567890123456-2'); | ||
expect(traceData.baggage).toEqual('sentry-environment=staging'); | ||
}); | ||
|
||
test('gets sentry trace data from the event if the context sentry trace is undefined', () => { | ||
const traceData = getAwsTraceData(mockEvent, { | ||
// @ts-expect-error, a partial context object is fine here | ||
clientContext: { Custom: { 'sentry-trace': undefined, baggage: '' } }, | ||
}); | ||
|
||
expect(traceData['sentry-trace']).toEqual('12345678901234567890123456789012-1234567890123456-2'); | ||
expect(traceData.baggage).toEqual('sentry-environment=staging'); | ||
}); | ||
}); | ||
|
||
describe('eventContextExtractor', () => { | ||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
test('passes sentry trace data to the propagation extractor', () => { | ||
// @ts-expect-error, a partial context object is fine here | ||
eventContextExtractor(mockEvent, mockContext); | ||
|
||
// @ts-expect-error, a partial context object is fine here | ||
const expectedTraceData = getAwsTraceData(mockEvent, mockContext); | ||
|
||
expect(mockExtractContext).toHaveBeenCalledTimes(1); | ||
expect(mockExtractContext).toHaveBeenCalledWith(expect.arrayContaining([expectedTraceData])); | ||
}); | ||
|
||
test('passes along non-sentry trace headers along', () => { | ||
eventContextExtractor( | ||
{ | ||
...mockEvent, | ||
headers: { | ||
...mockEvent.headers, | ||
'X-Custom-Header': 'Foo', | ||
}, | ||
}, | ||
// @ts-expect-error, a partial context object is fine here | ||
mockContext, | ||
); | ||
|
||
const expectedHeaders = { | ||
'X-Custom-Header': 'Foo', | ||
// @ts-expect-error, a partial context object is fine here | ||
...getAwsTraceData(mockEvent, mockContext), | ||
}; | ||
|
||
expect(mockExtractContext).toHaveBeenCalledTimes(1); | ||
expect(mockExtractContext).toHaveBeenCalledWith(expect.arrayContaining([expectedHeaders])); | ||
}); | ||
}); |