|
1 | | -import { waitForTransaction } from '@sentry-internal/test-utils'; |
| 1 | +import type { SerializedStreamedSpan } from '@sentry-internal/test-utils'; |
| 2 | +import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils'; |
2 | 3 | import { InvokeCommand } from '@aws-sdk/client-lambda'; |
3 | 4 | import { test, expect } from './lambda-fixtures'; |
4 | 5 |
|
5 | | -test.describe('NPM package', () => { |
6 | | - test('tracing in CJS works', async ({ lambdaClient }) => { |
7 | | - const transactionEventPromise = waitForTransaction('aws-serverless', transactionEvent => { |
8 | | - return transactionEvent?.transaction === 'NpmTracingCjs'; |
9 | | - }); |
10 | | - |
11 | | - await lambdaClient.send( |
12 | | - new InvokeCommand({ |
13 | | - FunctionName: 'NpmTracingCjs', |
14 | | - Payload: JSON.stringify({}), |
15 | | - }), |
16 | | - ); |
17 | | - |
18 | | - const transactionEvent = await transactionEventPromise; |
19 | | - |
20 | | - // shows the SDK sent a transaction |
21 | | - expect(transactionEvent.transaction).toEqual('NpmTracingCjs'); // name should be the function name |
22 | | - expect(transactionEvent.contexts?.trace).toEqual({ |
23 | | - data: { |
24 | | - 'sentry.sample_rate': 1, |
25 | | - 'sentry.segment.name.source': 'custom', |
26 | | - 'sentry.origin': 'auto.aws_lambda', |
27 | | - 'sentry.op': 'function.aws', |
28 | | - 'cloud.account.id': '012345678912', |
29 | | - 'cloud.platform': 'aws_lambda', |
30 | | - 'cloud.provider': 'aws', |
31 | | - 'faas.execution': expect.any(String), |
32 | | - 'faas.id': 'arn:aws:lambda:us-east-1:012345678912:function:NpmTracingCjs', |
33 | | - 'faas.name': 'NpmTracingCjs', |
34 | | - 'faas.coldstart': true, |
35 | | - 'sentry.kind': 'server', |
36 | | - }, |
37 | | - op: 'function.aws', |
38 | | - origin: 'auto.aws_lambda', |
39 | | - span_id: expect.stringMatching(/[a-f0-9]{16}/), |
40 | | - status: 'ok', |
41 | | - trace_id: expect.stringMatching(/[a-f0-9]{32}/), |
42 | | - }); |
43 | | - |
44 | | - expect(transactionEvent.spans).toHaveLength(2); |
| 6 | +// This app runs with `traceLifecycle: 'stream'`, the SDK default. The `aws-serverless-layer` app |
| 7 | +// covers the `'static'` lifecycle, so between the two both lifecycles stay under test. |
| 8 | + |
| 9 | +function assertLambdaTrace(spans: SerializedStreamedSpan[], functionName: string): void { |
| 10 | + const segmentSpan = spans.find(span => span.is_segment); |
| 11 | + |
| 12 | + // `function.aws` span names are low cardinality: the function name, never the invocation URL. |
| 13 | + expect(segmentSpan?.name).toBe(functionName); |
| 14 | + expect(segmentSpan?.status).toBe('ok'); |
| 15 | + expect(getSpanOp(segmentSpan!)).toBe('function.aws'); |
| 16 | + |
| 17 | + expect(segmentSpan?.attributes).toMatchObject({ |
| 18 | + 'sentry.op': { value: 'function.aws', type: 'string' }, |
| 19 | + 'sentry.origin': { value: 'auto.aws_lambda', type: 'string' }, |
| 20 | + 'sentry.kind': { value: 'server', type: 'string' }, |
| 21 | + 'sentry.segment.name.source': { value: 'custom', type: 'string' }, |
| 22 | + 'cloud.account.id': { value: '012345678912', type: 'string' }, |
| 23 | + 'cloud.platform': { value: 'aws_lambda', type: 'string' }, |
| 24 | + 'cloud.provider': { value: 'aws', type: 'string' }, |
| 25 | + 'faas.coldstart': { value: true, type: 'boolean' }, |
| 26 | + 'faas.execution': { value: expect.any(String), type: 'string' }, |
| 27 | + 'faas.id': { value: `arn:aws:lambda:us-east-1:012345678912:function:${functionName}`, type: 'string' }, |
| 28 | + // The name the span is named after also stays on the span, so it survives a rename. |
| 29 | + 'faas.name': { value: functionName, type: 'string' }, |
| 30 | + // Streamed spans have no event contexts, so the `aws.lambda` context the transaction used to |
| 31 | + // carry is stamped onto the segment span by `awsLambdaIntegration`. |
| 32 | + 'aws.lambda.function_name': { value: functionName, type: 'string' }, |
| 33 | + 'aws.lambda.invoked_function_arn': { |
| 34 | + value: `arn:aws:lambda:us-east-1:012345678912:function:${functionName}`, |
| 35 | + type: 'string', |
| 36 | + }, |
| 37 | + 'aws.lambda.aws_request_id': { value: expect.any(String), type: 'string' }, |
| 38 | + 'aws.cloudwatch.logs.log_group': { value: expect.any(String), type: 'string' }, |
| 39 | + 'aws.cloudwatch.logs.log_stream': { value: expect.any(String), type: 'string' }, |
| 40 | + }); |
45 | 41 |
|
46 | | - // shows that the Otel Http instrumentation is working |
47 | | - expect(transactionEvent.spans).toContainEqual( |
48 | | - expect.objectContaining({ |
49 | | - data: expect.objectContaining({ |
50 | | - 'sentry.op': 'http.client', |
51 | | - 'sentry.origin': 'auto.http.client', |
52 | | - 'url.full': 'http://example.com/', |
53 | | - }), |
54 | | - description: 'GET http://example.com/', |
55 | | - op: 'http.client', |
| 42 | + // shows that the Otel Http instrumentation is working |
| 43 | + expect(spans).toContainEqual( |
| 44 | + expect.objectContaining({ |
| 45 | + name: 'GET example.com', |
| 46 | + parent_span_id: segmentSpan?.span_id, |
| 47 | + attributes: expect.objectContaining({ |
| 48 | + 'sentry.op': { value: 'http.client', type: 'string' }, |
| 49 | + 'sentry.origin': { value: 'auto.http.client', type: 'string' }, |
| 50 | + 'url.full': { value: 'http://example.com/', type: 'string' }, |
56 | 51 | }), |
57 | | - ); |
58 | | - |
59 | | - // shows that the manual span creation is working |
60 | | - expect(transactionEvent.spans).toContainEqual( |
61 | | - expect.objectContaining({ |
62 | | - data: expect.objectContaining({ |
63 | | - 'sentry.op': 'manual', |
64 | | - 'sentry.origin': 'manual', |
65 | | - }), |
66 | | - description: 'manual-span', |
67 | | - op: 'manual', |
| 52 | + }), |
| 53 | + ); |
| 54 | + |
| 55 | + // shows that the manual span creation is working |
| 56 | + expect(spans).toContainEqual( |
| 57 | + expect.objectContaining({ |
| 58 | + name: 'manual-span', |
| 59 | + parent_span_id: segmentSpan?.span_id, |
| 60 | + attributes: expect.objectContaining({ |
| 61 | + 'sentry.op': { value: 'manual', type: 'string' }, |
| 62 | + 'sentry.origin': { value: 'manual', type: 'string' }, |
68 | 63 | }), |
69 | | - ); |
70 | | - |
71 | | - // shows that the SDK source is correctly detected |
72 | | - expect(transactionEvent.sdk?.packages).toContainEqual( |
73 | | - expect.objectContaining({ name: 'npm:@sentry/aws-serverless' }), |
74 | | - ); |
75 | | - }); |
| 64 | + }), |
| 65 | + ); |
| 66 | +} |
76 | 67 |
|
77 | | - test('tracing in ESM works', async ({ lambdaClient }) => { |
78 | | - const transactionEventPromise = waitForTransaction('aws-serverless', transactionEvent => { |
79 | | - return transactionEvent?.transaction === 'NpmTracingEsm'; |
80 | | - }); |
| 68 | +test.describe('NPM package', () => { |
| 69 | + for (const [label, functionName] of [ |
| 70 | + ['CJS', 'NpmTracingCjs'], |
| 71 | + ['ESM', 'NpmTracingEsm'], |
| 72 | + ] as const) { |
| 73 | + test(`tracing in ${label} works`, async ({ lambdaClient }) => { |
| 74 | + const spansPromise = collectStreamedSpans('aws-serverless', spansOfTrace => |
| 75 | + spansOfTrace.some(span => span.is_segment && span.name === functionName), |
| 76 | + ); |
81 | 77 |
|
82 | | - await lambdaClient.send( |
83 | | - new InvokeCommand({ |
84 | | - FunctionName: 'NpmTracingEsm', |
85 | | - Payload: JSON.stringify({}), |
86 | | - }), |
87 | | - ); |
| 78 | + await lambdaClient.send(new InvokeCommand({ FunctionName: functionName, Payload: JSON.stringify({}) })); |
88 | 79 |
|
89 | | - const transactionEvent = await transactionEventPromise; |
| 80 | + const spans = await spansPromise; |
90 | 81 |
|
91 | | - // shows the SDK sent a transaction |
92 | | - expect(transactionEvent.transaction).toEqual('NpmTracingEsm'); // name should be the function name |
93 | | - expect(transactionEvent.contexts?.trace).toEqual({ |
94 | | - data: { |
95 | | - 'sentry.sample_rate': 1, |
96 | | - 'sentry.segment.name.source': 'custom', |
97 | | - 'sentry.origin': 'auto.aws_lambda', |
98 | | - 'sentry.op': 'function.aws', |
99 | | - 'cloud.account.id': '012345678912', |
100 | | - 'cloud.platform': 'aws_lambda', |
101 | | - 'cloud.provider': 'aws', |
102 | | - 'faas.execution': expect.any(String), |
103 | | - 'faas.id': 'arn:aws:lambda:us-east-1:012345678912:function:NpmTracingEsm', |
104 | | - 'faas.name': 'NpmTracingEsm', |
105 | | - 'faas.coldstart': true, |
106 | | - 'sentry.kind': 'server', |
107 | | - }, |
108 | | - op: 'function.aws', |
109 | | - origin: 'auto.aws_lambda', |
110 | | - span_id: expect.stringMatching(/[a-f0-9]{16}/), |
111 | | - status: 'ok', |
112 | | - trace_id: expect.stringMatching(/[a-f0-9]{32}/), |
| 82 | + assertLambdaTrace(spans, functionName); |
113 | 83 | }); |
114 | | - |
115 | | - expect(transactionEvent.spans).toHaveLength(2); |
116 | | - |
117 | | - // shows that the Otel Http instrumentation is working |
118 | | - expect(transactionEvent.spans).toContainEqual( |
119 | | - expect.objectContaining({ |
120 | | - data: expect.objectContaining({ |
121 | | - 'sentry.op': 'http.client', |
122 | | - 'sentry.origin': 'auto.http.client', |
123 | | - 'url.full': 'http://example.com/', |
124 | | - }), |
125 | | - description: 'GET http://example.com/', |
126 | | - op: 'http.client', |
127 | | - }), |
128 | | - ); |
129 | | - |
130 | | - // shows that the manual span creation is working |
131 | | - expect(transactionEvent.spans).toContainEqual( |
132 | | - expect.objectContaining({ |
133 | | - data: expect.objectContaining({ |
134 | | - 'sentry.op': 'manual', |
135 | | - 'sentry.origin': 'manual', |
136 | | - }), |
137 | | - description: 'manual-span', |
138 | | - op: 'manual', |
139 | | - }), |
140 | | - ); |
141 | | - |
142 | | - // shows that the SDK source is correctly detected |
143 | | - expect(transactionEvent.sdk?.packages).toContainEqual( |
144 | | - expect.objectContaining({ name: 'npm:@sentry/aws-serverless' }), |
145 | | - ); |
146 | | - }); |
| 84 | + } |
147 | 85 | }); |
0 commit comments