Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as Sentry from '@sentry/node';

async function run() {
const { default: Redis } = await import('ioredis-6');
const redisClient = new Redis({ host: '127.0.0.1', port: 6382, lazyConnect: true });

await redisClient.connect();
await redisClient.set('dc-no-parent:before', 'test-value');

const parent = Sentry.startInactiveSpan({ name: 'parent operation' });
await Sentry.withActiveSpan(parent, () => redisClient.set('dc-no-parent:inside', 'test-value'));

await redisClient.set('dc-no-parent:after', 'test-value');
await redisClient.disconnect();

// Flush before ending the parent, so a span without a parent would arrive before the parent does.
await Sentry.flush();
parent.end();
await Sentry.flush();
}

run();
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,8 @@ describeWithDockerCompose(
await createTestRunner()
.expect({
span: container => {
// The connect span opens its own segment but shares the trace with the test span,
// so both segments arrive in the same container.
expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([
'redis-connect',
SEGMENT_NAME,
]);
expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([SEGMENT_NAME]);

// ioredis' own handshake commands (`client SETINFO`, `info`) are emitted on the
// channel too, but belong to the connect segment — the test span's children are the
// commands the scenario issues.
const spans = container.items.filter(
item => !item.is_segment && item.attributes[SENTRY_SEGMENT_NAME]?.value === SEGMENT_NAME,
);
Expand Down Expand Up @@ -161,5 +153,26 @@ describeWithDockerCompose(
});
});
}

createEsmAndCjsTests(__dirname, 'scenario-no-parent.mjs', 'instrument.mjs', (createTestRunner, test) => {
test('only instruments commands with an active parent span', { timeout: 60_000 }, async () => {
const runner = createTestRunner();
// Spans without a parent share the scope's trace id, so they are collected with this trace.
const spansPromise = runner.collectStreamedSpansUntilSegment('parent operation');

await runner.start().completed();

const spans = await spansPromise;
const parent = spans.find(span => span.is_segment && span.name === 'parent operation')!;
expect(
spans
.filter(span => span.attributes[SENTRY_ORIGIN]?.value === ORIGIN)
.map(span => ({
query: span.attributes[DB_QUERY_TEXT]?.value ?? span.name,
parentId: span.parent_span_id,
})),
).toEqual([{ query: 'set dc-no-parent:inside ?', parentId: parent.span_id }]);
});
});
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as Sentry from '@sentry/node';
import { createClient } from 'redis-4';

async function run() {
const redisClient = await createClient({ socket: { port: 6383 } }).connect();

await redisClient.set('redis-4-no-parent:before', 'test-value');
await redisClient.multi().set('redis-4-no-parent:multi-before', 'test-value').exec();

const parent = Sentry.startInactiveSpan({ name: 'parent operation' });
await Sentry.withActiveSpan(parent, async () => {
await redisClient.set('redis-4-no-parent:inside', 'test-value');
await redisClient.multi().set('redis-4-no-parent:multi-inside', 'test-value').exec();
});

await redisClient.set('redis-4-no-parent:after', 'test-value');
await redisClient.disconnect();

// Flush before ending the parent, so a span without a parent would arrive before the parent does.
await Sentry.flush();
parent.end();
await Sentry.flush();
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as Sentry from '@sentry/node';
import { createClient } from 'redis-5';

async function run() {
const redisClient = await createClient({ socket: { port: 6383 } }).connect();

await redisClient.set('redis-5-no-parent:before', 'test-value');
await redisClient.multi().set('redis-5-no-parent:multi-before', 'test-value').exec();

const parent = Sentry.startInactiveSpan({ name: 'parent operation' });
await Sentry.withActiveSpan(parent, async () => {
await redisClient.set('redis-5-no-parent:inside', 'test-value');
await redisClient.multi().set('redis-5-no-parent:multi-inside', 'test-value').exec();
});

await redisClient.set('redis-5-no-parent:after', 'test-value');
await redisClient.disconnect();

// Flush before ending the parent, so a span without a parent would arrive before the parent does.
await Sentry.flush();
parent.end();
await Sentry.flush();
}

run();
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,7 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory
await createTestRunner()
.expect({
span: container => {
// The connect span opens its own segment, but shares the trace with the test span,
// so both segments arrive in the same container.
expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([
'redis-connect',
segmentName,
]);
expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([segmentName]);

expect(childSpans(container)).toEqual([
span('SET localhost:6383', redisSpanOp, {
Expand Down Expand Up @@ -427,10 +422,7 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory
await createTestRunner()
.expect({
span: container => {
expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([
'redis-connect',
segmentName,
]);
expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([segmentName]);

expect(childSpans(container)).toEqual([
span('SET localhost:6383', redisSpanOp, {
Expand Down Expand Up @@ -505,4 +497,41 @@ describeWithDockerCompose('redis cache auto instrumentation', { workingDirectory
});
});
});

describe('no parent span', () => {
for (const version of [4, 5]) {
createEsmAndCjsTests(
__dirname,
`scenario-no-parent-redis-${version}.mjs`,
`instrument-redis-${version}.mjs`,
(createTestRunner, test) => {
test(
`only instruments commands with an active parent span (redis-${version})`,
{ timeout: 60_000 },
async () => {
const runner = createTestRunner();
// Spans without a parent share the scope's trace id, so they are collected with this trace.
const spansPromise = runner.collectStreamedSpansUntilSegment('parent operation');

await runner.start().completed();

const spans = await spansPromise;
const parent = spans.find(span => span.is_segment && span.name === 'parent operation')!;
expect(
spans
.filter(span => span.attributes[SENTRY_ORIGIN]?.value === redisOrigin)
.map(span => ({
query: span.attributes[DB_QUERY_TEXT]?.value ?? span.name,
parentId: span.parent_span_id,
})),
).toEqual([
{ query: `SET redis-${version}-no-parent:inside [1 other arguments]`, parentId: parent.span_id },
{ query: 'MULTI', parentId: parent.span_id },
]);
},
);
},
);
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as Sentry from '@sentry/node';

async function run() {
const { createClient } = await import('redis-5-tracing');
const redisClient = await createClient({ socket: { host: '127.0.0.1', port: 6381 } }).connect();

await redisClient.set('dc-no-parent:before', 'test-value');
await redisClient.multi().set('dc-no-parent:multi-before', 'test-value').exec();

const parent = Sentry.startInactiveSpan({ name: 'parent operation' });
await Sentry.withActiveSpan(parent, async () => {
await redisClient.set('dc-no-parent:inside', 'test-value');
await redisClient.multi().set('dc-no-parent:multi-inside', 'test-value').exec();
});

await redisClient.set('dc-no-parent:after', 'test-value');
await redisClient.disconnect();

// Flush before ending the parent, so a span without a parent would arrive before the parent does.
await Sentry.flush();
parent.end();
await Sentry.flush();
}

run();
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,7 @@ describeWithDockerCompose(
await createTestRunner()
.expect({
span: container => {
// The connect span opens its own segment but shares the trace with the test span,
// so both segments arrive in the same container.
expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([
'redis-connect',
SEGMENT_NAME,
]);
expect(container.items.filter(item => item.is_segment).map(item => item.name)).toEqual([SEGMENT_NAME]);

const spans = container.items.filter(
item => !item.is_segment && item.attributes[SENTRY_SEGMENT_NAME]?.value === SEGMENT_NAME,
Expand Down Expand Up @@ -204,5 +199,29 @@ describeWithDockerCompose(
.completed();
});
});

createEsmAndCjsTests(__dirname, 'scenario-no-parent.mjs', 'instrument.mjs', (createTestRunner, test) => {
test('only instruments commands with an active parent span', { timeout: 60_000 }, async () => {
const runner = createTestRunner();
// Spans without a parent share the scope's trace id, so they are collected with this trace.
const spansPromise = runner.collectStreamedSpansUntilSegment('parent operation');

await runner.start().completed();

const spans = await spansPromise;
const parent = spans.find(span => span.is_segment && span.name === 'parent operation')!;
expect(
spans
.filter(span => span.attributes[SENTRY_ORIGIN]?.value === ORIGIN)
.map(span => ({
query: span.attributes[DB_QUERY_TEXT]?.value ?? span.name,
parentId: span.parent_span_id,
})),
).toEqual([
{ query: 'SET dc-no-parent:inside ?', parentId: parent.span_id },
{ query: 'MULTI', parentId: parent.span_id },
]);
});
});
},
);
Loading
Loading