Skip to content

Commit d8f8243

Browse files
nicohrubeccodex
andcommitted
test(node): Port AMQP integration tests to span streaming
Co-Authored-By: GPT-6 <codex@openai.com>
1 parent ca05d2d commit d8f8243

3 files changed

Lines changed: 64 additions & 119 deletions

File tree

‎dev-packages/node-integration-tests/suites/tracing/amqplib/instrument-span-streaming.mjs‎

Lines changed: 0 additions & 10 deletions
This file was deleted.

‎dev-packages/node-integration-tests/suites/tracing/amqplib/instrument.mjs‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as Sentry from '@sentry/node';
22
import { loggingTransport } from '@sentry-internal/node-integration-tests';
33

44
Sentry.init({
5-
traceLifecycle: 'static',
65
dsn: 'https://public@dsn.ingest.sentry.io/1337',
76
release: '1.0',
87
tracesSampleRate: 1.0,
Lines changed: 64 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1-
import type { TransactionEvent } from '@sentry/core';
1+
import {
2+
MESSAGING_DESTINATION_NAME,
3+
MESSAGING_OPERATION_NAME,
4+
MESSAGING_OPERATION_TYPE,
5+
MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY,
6+
MESSAGING_SYSTEM,
7+
NETWORK_PROTOCOL_NAME,
8+
NETWORK_PROTOCOL_VERSION,
9+
SENTRY_KIND,
10+
SENTRY_OP,
11+
SENTRY_ORIGIN,
12+
SERVER_ADDRESS,
13+
SERVER_PORT,
14+
URL_FULL,
15+
} from '@sentry/conventions/attributes';
216
import { afterAll, describe, expect } from 'vitest';
317
import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose } from '../../../utils/runner';
418

@@ -8,37 +22,35 @@ import { cleanupChildProcesses, createEsmAndCjsTests, describeWithDockerCompose
822
// default (empty) exchange with the queue name as the routing key.
923
const expectedProducerSpan = (routingKey: string) =>
1024
expect.objectContaining({
11-
op: 'queue.publish',
12-
data: expect.objectContaining({
13-
'messaging.system': 'rabbitmq',
14-
'messaging.operation.name': 'send',
15-
'messaging.operation.type': 'send',
16-
'messaging.destination.name': routingKey,
17-
'messaging.rabbitmq.destination.routing_key': routingKey,
18-
'network.protocol.name': 'AMQP',
19-
'network.protocol.version': '0.9.1',
20-
'server.address': 'localhost',
21-
'server.port': 5672,
22-
'url.full': 'amqp://sentry:***@localhost:5672/',
23-
'sentry.kind': 'producer',
24-
'sentry.op': 'queue.publish',
25-
'sentry.origin': 'auto.amqplib.publisher',
25+
attributes: expect.objectContaining({
26+
[MESSAGING_SYSTEM]: { type: 'string', value: 'rabbitmq' },
27+
[MESSAGING_OPERATION_NAME]: { type: 'string', value: 'send' },
28+
[MESSAGING_OPERATION_TYPE]: { type: 'string', value: 'send' },
29+
[MESSAGING_DESTINATION_NAME]: { type: 'string', value: routingKey },
30+
[MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY]: { type: 'string', value: routingKey },
31+
[NETWORK_PROTOCOL_NAME]: { type: 'string', value: 'AMQP' },
32+
[NETWORK_PROTOCOL_VERSION]: { type: 'string', value: '0.9.1' },
33+
[SERVER_ADDRESS]: { type: 'string', value: 'localhost' },
34+
[SERVER_PORT]: { type: 'integer', value: 5672 },
35+
[URL_FULL]: { type: 'string', value: 'amqp://sentry:***@localhost:5672/' },
36+
[SENTRY_KIND]: { type: 'string', value: 'producer' },
37+
[SENTRY_OP]: { type: 'string', value: 'queue.publish' },
38+
[SENTRY_ORIGIN]: { type: 'string', value: 'auto.amqplib.publisher' },
2639
}),
2740
status: 'ok',
2841
});
2942

3043
const EXPECTED_MESSAGE_SPAN_CONSUMER = expect.objectContaining({
31-
op: 'queue.process',
32-
data: expect.objectContaining({
33-
'messaging.system': 'rabbitmq',
44+
attributes: expect.objectContaining({
45+
[MESSAGING_SYSTEM]: { type: 'string', value: 'rabbitmq' },
3446
// The delivery carries the default exchange (''), so the routing key is the destination.
35-
'messaging.destination.name': 'queue1',
36-
'messaging.rabbitmq.destination.routing_key': 'queue1',
37-
'messaging.operation.name': 'process',
38-
'messaging.operation.type': 'process',
39-
'sentry.kind': 'consumer',
40-
'sentry.op': 'queue.process',
41-
'sentry.origin': 'auto.amqplib.consumer',
47+
[MESSAGING_DESTINATION_NAME]: { type: 'string', value: 'queue1' },
48+
[MESSAGING_RABBITMQ_DESTINATION_ROUTING_KEY]: { type: 'string', value: 'queue1' },
49+
[MESSAGING_OPERATION_NAME]: { type: 'string', value: 'process' },
50+
[MESSAGING_OPERATION_TYPE]: { type: 'string', value: 'process' },
51+
[SENTRY_KIND]: { type: 'string', value: 'consumer' },
52+
[SENTRY_OP]: { type: 'string', value: 'queue.process' },
53+
[SENTRY_ORIGIN]: { type: 'string', value: 'auto.amqplib.consumer' },
4254
}),
4355
status: 'ok',
4456
});
@@ -59,40 +71,27 @@ describeWithDockerCompose('amqplib auto-instrumentation', { workingDirectory: [_
5971
'instrument.mjs',
6072
(createTestRunner, test) => {
6173
test('should be able to send and receive messages', { timeout: 60_000 }, async () => {
62-
// The producer ('root span') and consumer ('queue1 process') transactions can
63-
// arrive in any order, so we collect them and assert after both are received.
64-
const receivedTransactions: TransactionEvent[] = [];
65-
6674
await createTestRunner()
6775
.expect({
68-
transaction: (transaction: TransactionEvent) => {
69-
receivedTransactions.push(transaction);
70-
},
71-
})
72-
.expect({
73-
transaction: (transaction: TransactionEvent) => {
74-
receivedTransactions.push(transaction);
75-
76-
// The producer span is a child of the manually-started 'root span' transaction, so we
77-
// identify it by its origin rather than by transaction name. The consumer span is its
78-
// own transaction, identified by the origin on its trace context.
79-
const producer = receivedTransactions.find(t =>
80-
t.spans?.some(s => s.data?.['sentry.origin'] === 'auto.amqplib.publisher'),
81-
);
82-
const consumer = receivedTransactions.find(
83-
t => t.contexts?.trace?.data?.['sentry.origin'] === 'auto.amqplib.consumer',
76+
span: container => {
77+
const producer = container.items.find(span => span.is_segment && span.name === 'root span');
78+
const consumer = container.items.find(
79+
t => t.attributes[SENTRY_ORIGIN]?.value === 'auto.amqplib.consumer',
8480
);
8581

8682
expect(producer).toBeDefined();
8783
expect(consumer).toBeDefined();
8884

89-
expect(producer!.transaction).toBe('root span');
90-
expect(consumer!.transaction).toBe('queue1 process');
85+
expect(producer!.name).toBe('root span');
86+
expect(consumer!.name).toBe('process queue1');
9187

92-
const producerSpan = producer!.spans?.find(s => s.data?.['sentry.origin'] === 'auto.amqplib.publisher');
88+
const producerSpan = container.items.find(
89+
s => s.attributes[SENTRY_ORIGIN]?.value === 'auto.amqplib.publisher',
90+
);
91+
expect(producerSpan?.name).toBe('send queue1');
9392
expect(producerSpan).toMatchObject(expectedProducerSpan('queue1'));
9493

95-
expect(consumer!.contexts?.trace).toMatchObject(EXPECTED_MESSAGE_SPAN_CONSUMER);
94+
expect(consumer!).toMatchObject(EXPECTED_MESSAGE_SPAN_CONSUMER);
9695
},
9796
})
9897
.start()
@@ -108,35 +107,23 @@ describeWithDockerCompose('amqplib auto-instrumentation', { workingDirectory: [_
108107
'instrument.mjs',
109108
(createTestRunner, test) => {
110109
test('marks the consumer span as errored when the message is rejected', { timeout: 60_000 }, async () => {
111-
// The error scenario emits the producer ('root span') and the rejected consumer
112-
// ('queue1 process') transactions in any order, so we collect both and assert on the consumer.
113-
const receivedTransactions: TransactionEvent[] = [];
114-
115110
await createTestRunner()
116111
.expect({
117-
transaction: (transaction: TransactionEvent) => {
118-
receivedTransactions.push(transaction);
119-
},
120-
})
121-
.expect({
122-
transaction: (transaction: TransactionEvent) => {
123-
receivedTransactions.push(transaction);
124-
125-
const consumer = receivedTransactions.find(
126-
t => t.contexts?.trace?.data?.['sentry.origin'] === 'auto.amqplib.consumer',
112+
span: container => {
113+
const consumer = container.items.find(
114+
t => t.attributes[SENTRY_ORIGIN]?.value === 'auto.amqplib.consumer',
127115
);
128116

129117
expect(consumer).toBeDefined();
130-
expect(consumer!.transaction).toBe('queue-error process');
131-
expect(consumer!.contexts?.trace).toMatchObject(
118+
expect(consumer!.name).toBe('process queue-error');
119+
expect(consumer!).toMatchObject(
132120
expect.objectContaining({
133-
op: 'queue.process',
134-
status: 'internal_error',
135-
data: expect.objectContaining({
136-
'messaging.system': 'rabbitmq',
137-
'sentry.kind': 'consumer',
138-
'sentry.op': 'queue.process',
139-
'sentry.origin': 'auto.amqplib.consumer',
121+
status: 'error',
122+
attributes: expect.objectContaining({
123+
[MESSAGING_SYSTEM]: { type: 'string', value: 'rabbitmq' },
124+
[SENTRY_KIND]: { type: 'string', value: 'consumer' },
125+
[SENTRY_OP]: { type: 'string', value: 'queue.process' },
126+
[SENTRY_ORIGIN]: { type: 'string', value: 'auto.amqplib.consumer' },
140127
}),
141128
}),
142129
);
@@ -160,11 +147,11 @@ describeWithDockerCompose('amqplib auto-instrumentation', { workingDirectory: [_
160147
async () => {
161148
await createTestRunner()
162149
.expect({
163-
transaction: (transaction: TransactionEvent) => {
164-
expect(transaction.transaction).toBe('root span');
150+
span: container => {
151+
expect(container.items.find(span => span.is_segment)?.name).toBe('root span');
165152

166-
const producerSpans = transaction.spans?.filter(
167-
s => s.data?.['sentry.origin'] === 'auto.amqplib.publisher',
153+
const producerSpans = container.items.filter(
154+
s => s.attributes[SENTRY_ORIGIN]?.value === 'auto.amqplib.publisher',
168155
);
169156

170157
// The confirm channel internally calls the base publish; the instrumentation must not
@@ -181,35 +168,4 @@ describeWithDockerCompose('amqplib auto-instrumentation', { workingDirectory: [_
181168
{ additionalDependencies },
182169
);
183170
});
184-
185-
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-span-streaming.mjs', (createTestRunner, test) => {
186-
test('names streamed spans after the messaging conventions', { timeout: 60_000 }, async () => {
187-
await createTestRunner()
188-
.ignore('event')
189-
.expect({
190-
span: container => {
191-
// `sendToQueue` publishes to the default exchange, which has no name. Its routing key is the
192-
// queue name, so it is the destination rather than per-message data.
193-
for (const origin of ['auto.amqplib.publisher', 'auto.amqplib.consumer']) {
194-
const span = container.items.find(item => item.attributes['sentry.origin']?.value === origin);
195-
expect(span).toBeDefined();
196-
expect(span!.attributes['messaging.destination.name']?.value).toBe('queue1');
197-
expect(span!.attributes['messaging.rabbitmq.destination.routing_key']?.value).toBe('queue1');
198-
}
199-
200-
const producerSpan = container.items.find(
201-
span => span.attributes['sentry.origin']?.value === 'auto.amqplib.publisher',
202-
);
203-
expect(producerSpan!.name).toBe('send queue1');
204-
205-
const consumerSpan = container.items.find(
206-
span => span.attributes['sentry.origin']?.value === 'auto.amqplib.consumer',
207-
);
208-
expect(consumerSpan!.name).toBe('process queue1');
209-
},
210-
})
211-
.start()
212-
.completed();
213-
});
214-
});
215171
});

0 commit comments

Comments
 (0)