|
1 | 1 | import { expect, test } from '@playwright/test'; |
2 | | -import { waitForTransaction } from '@sentry-internal/test-utils'; |
| 2 | +import { collectStreamedSpans, getSpanOp } from '@sentry-internal/test-utils'; |
3 | 3 | import { APP_NAME } from '../constants'; |
4 | 4 |
|
5 | 5 | // Same spans in both runs, from two injectors: the build-time transform in the server bundle, and |
6 | 6 | // the runtime hook in `react-router dev`, where the drivers stay on Node's own loader. |
7 | 7 | test.describe('server - orchestrion db instrumentation', () => { |
8 | 8 | test('instruments ioredis automatically via orchestrion', async ({ page }) => { |
9 | | - const transactionEventPromise = waitForTransaction(APP_NAME, transactionEvent => { |
10 | | - return ( |
11 | | - transactionEvent.contexts?.trace?.op === 'http.server' && |
12 | | - transactionEvent.transaction === 'GET /performance/db-ioredis' |
13 | | - ); |
14 | | - }); |
| 9 | + const spansPromise = collectStreamedSpans(APP_NAME, spansOfTrace => |
| 10 | + spansOfTrace.some(span => span.name === 'GET /performance/db-ioredis' && span.is_segment), |
| 11 | + ); |
15 | 12 |
|
16 | 13 | await page.goto('/performance/db-ioredis'); |
17 | 14 |
|
18 | | - const transactionEvent = await transactionEventPromise; |
19 | | - const spans = transactionEvent.spans || []; |
| 15 | + const spans = await spansPromise; |
| 16 | + const segmentSpan = spans.find(span => span.name === 'GET /performance/db-ioredis' && span.is_segment)!; |
20 | 17 |
|
21 | | - // The server transaction must come from the native instrumentation API (not the legacy handler), |
| 18 | + // The server segment must come from the native instrumentation API (not the legacy handler), |
22 | 19 | // proving the orchestrion-injected db spans share context with the React Router server span. |
23 | | - expect(transactionEvent.contexts?.trace?.origin).toBe('auto.http.react_router.instrumentation_api'); |
| 20 | + expect(getSpanOp(segmentSpan)).toBe('http.server'); |
| 21 | + expect(segmentSpan.attributes['sentry.origin']?.value).toBe('auto.http.react_router.instrumentation_api'); |
24 | 22 |
|
25 | | - expect(spans).toContainEqual( |
| 23 | + const childSpans = spans.filter(span => !span.is_segment); |
| 24 | + |
| 25 | + expect(childSpans).toContainEqual( |
26 | 26 | expect.objectContaining({ |
27 | | - op: 'db.query', |
28 | | - origin: 'auto.db.redis', |
29 | | - description: 'set test-key [1 other arguments]', |
| 27 | + name: 'set test-key [1 other arguments]', |
30 | 28 | status: 'ok', |
31 | | - data: expect.objectContaining({ |
32 | | - 'db.system.name': 'redis', |
33 | | - 'db.operation.name': 'set', |
34 | | - 'db.query.text': 'set test-key [1 other arguments]', |
| 29 | + attributes: expect.objectContaining({ |
| 30 | + 'sentry.op': { value: 'db.query', type: 'string' }, |
| 31 | + 'sentry.origin': { value: 'auto.db.redis', type: 'string' }, |
| 32 | + 'db.system.name': { value: 'redis', type: 'string' }, |
| 33 | + 'db.operation.name': { value: 'set', type: 'string' }, |
| 34 | + 'db.query.text': { value: 'set test-key [1 other arguments]', type: 'string' }, |
35 | 35 | }), |
36 | 36 | }), |
37 | 37 | ); |
38 | | - expect(spans).toContainEqual( |
| 38 | + expect(childSpans).toContainEqual( |
39 | 39 | expect.objectContaining({ |
40 | | - op: 'db.query', |
41 | | - origin: 'auto.db.redis', |
42 | | - description: 'get test-key', |
| 40 | + name: 'get test-key', |
43 | 41 | status: 'ok', |
44 | | - data: expect.objectContaining({ |
45 | | - 'db.system.name': 'redis', |
46 | | - 'db.operation.name': 'get', |
47 | | - 'db.query.text': 'get test-key', |
| 42 | + attributes: expect.objectContaining({ |
| 43 | + 'sentry.op': { value: 'db.query', type: 'string' }, |
| 44 | + 'sentry.origin': { value: 'auto.db.redis', type: 'string' }, |
| 45 | + 'db.system.name': { value: 'redis', type: 'string' }, |
| 46 | + 'db.operation.name': { value: 'get', type: 'string' }, |
| 47 | + 'db.query.text': { value: 'get test-key', type: 'string' }, |
48 | 48 | }), |
49 | 49 | }), |
50 | 50 | ); |
51 | 51 |
|
52 | 52 | // Each command maps to exactly one span (no offline-queue duplicate). |
53 | | - const setSpans = spans.filter(span => span.description === 'set test-key [1 other arguments]'); |
| 53 | + const setSpans = spans.filter(span => span.name === 'set test-key [1 other arguments]'); |
54 | 54 | expect(setSpans).toHaveLength(1); |
55 | 55 |
|
56 | | - // Every db span nests under the native instrumentation-API http.server transaction. |
57 | | - const rootSpanId = transactionEvent.contexts?.trace?.span_id; |
58 | | - const spanIds = new Set([rootSpanId, ...spans.map(span => span.span_id)]); |
59 | | - const dbSpans = spans.filter(span => span.origin === 'auto.db.redis'); |
| 56 | + // Every db span nests under the native instrumentation-API http.server segment. |
| 57 | + const spanIds = new Set(spans.filter(span => span.trace_id === segmentSpan.trace_id).map(span => span.span_id)); |
| 58 | + const dbSpans = spans.filter(span => span.attributes['sentry.origin']?.value === 'auto.db.redis'); |
60 | 59 | expect(dbSpans.every(span => typeof span.parent_span_id === 'string' && spanIds.has(span.parent_span_id))).toBe( |
61 | 60 | true, |
62 | 61 | ); |
63 | 62 | }); |
64 | 63 |
|
| 64 | + // Under span streaming the mysql span name is the query summary, so both queries below are named |
| 65 | + // `SELECT`. `db.query.text` is what tells them apart. |
65 | 66 | test('instruments mysql automatically via orchestrion', async ({ page }) => { |
66 | | - const transactionEventPromise = waitForTransaction(APP_NAME, transactionEvent => { |
67 | | - return ( |
68 | | - transactionEvent.contexts?.trace?.op === 'http.server' && |
69 | | - transactionEvent.transaction === 'GET /performance/db-mysql' |
70 | | - ); |
71 | | - }); |
| 67 | + const spansPromise = collectStreamedSpans(APP_NAME, spansOfTrace => |
| 68 | + spansOfTrace.some(span => span.name === 'GET /performance/db-mysql' && span.is_segment), |
| 69 | + ); |
72 | 70 |
|
73 | 71 | await page.goto('/performance/db-mysql'); |
74 | 72 |
|
75 | | - const transactionEvent = await transactionEventPromise; |
76 | | - const spans = transactionEvent.spans || []; |
| 73 | + const spans = await spansPromise; |
77 | 74 |
|
78 | | - expect(spans).toContainEqual( |
79 | | - expect.objectContaining({ |
80 | | - op: 'db', |
81 | | - origin: 'auto.db.mysql', |
82 | | - description: 'SELECT 1 + 1 AS solution', |
83 | | - status: 'ok', |
84 | | - data: expect.objectContaining({ |
85 | | - 'db.system.name': 'mysql', |
86 | | - 'db.query.text': 'SELECT 1 + 1 AS solution', |
87 | | - 'db.user': 'root', |
88 | | - 'db.connection_string': expect.any(String), |
89 | | - 'server.address': expect.any(String), |
90 | | - 'server.port': 3306, |
| 75 | + for (const queryText of ['SELECT 1 + 1 AS solution', 'SELECT NOW()']) { |
| 76 | + expect(spans).toContainEqual( |
| 77 | + expect.objectContaining({ |
| 78 | + name: 'SELECT', |
| 79 | + status: 'ok', |
| 80 | + attributes: expect.objectContaining({ |
| 81 | + 'sentry.op': { value: 'db', type: 'string' }, |
| 82 | + 'sentry.origin': { value: 'auto.db.mysql', type: 'string' }, |
| 83 | + 'db.system.name': { value: 'mysql', type: 'string' }, |
| 84 | + 'db.query.text': { value: queryText, type: 'string' }, |
| 85 | + 'db.user': { value: 'root', type: 'string' }, |
| 86 | + 'db.connection_string': { value: expect.any(String), type: 'string' }, |
| 87 | + 'server.address': { value: expect.any(String), type: 'string' }, |
| 88 | + 'server.port': { value: 3306, type: 'integer' }, |
| 89 | + }), |
91 | 90 | }), |
92 | | - }), |
93 | | - ); |
94 | | - expect(spans).toContainEqual( |
95 | | - expect.objectContaining({ |
96 | | - op: 'db', |
97 | | - origin: 'auto.db.mysql', |
98 | | - description: 'SELECT NOW()', |
99 | | - status: 'ok', |
100 | | - data: expect.objectContaining({ |
101 | | - 'db.system.name': 'mysql', |
102 | | - 'db.query.text': 'SELECT NOW()', |
103 | | - 'db.user': 'root', |
104 | | - 'db.connection_string': expect.any(String), |
105 | | - 'server.address': expect.any(String), |
106 | | - 'server.port': 3306, |
107 | | - }), |
108 | | - }), |
109 | | - ); |
| 91 | + ); |
| 92 | + } |
110 | 93 | }); |
111 | 94 | }); |
0 commit comments