-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
test: Add tests to demonstrate root trace ID behavior #14426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Sentry.withScope(() => { | ||
Sentry.startSpan({ name: 'test_span_1' }, () => undefined); | ||
Sentry.startSpan({ name: 'test_span_2' }, () => undefined); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { expect } from '@playwright/test'; | ||
|
||
import type { TransactionEvent } from '@sentry/types'; | ||
import { sentryTest } from '../../../../utils/fixtures'; | ||
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; | ||
|
||
sentryTest( | ||
'should send manually started parallel root spans outside of root context', | ||
async ({ getLocalTestUrl, page }) => { | ||
if (shouldSkipTracingTest()) { | ||
sentryTest.skip(); | ||
} | ||
|
||
const url = await getLocalTestUrl({ testDir: __dirname }); | ||
|
||
const transaction1ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_1'); | ||
const transaction2ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_2'); | ||
|
||
await page.goto(url); | ||
|
||
const [transaction1Req, transaction2Req] = await Promise.all([transaction1ReqPromise, transaction2ReqPromise]); | ||
|
||
const transaction1 = envelopeRequestParser<TransactionEvent>(transaction1Req); | ||
const transaction2 = envelopeRequestParser<TransactionEvent>(transaction2Req); | ||
|
||
expect(transaction1).toBeDefined(); | ||
expect(transaction2).toBeDefined(); | ||
|
||
const trace1Id = transaction1.contexts?.trace?.trace_id; | ||
const trace2Id = transaction2.contexts?.trace?.trace_id; | ||
|
||
expect(trace1Id).toBeDefined(); | ||
expect(trace2Id).toBeDefined(); | ||
|
||
// We use the same traceID from the root propagation context here | ||
expect(trace1Id).toBe(trace2Id); | ||
|
||
expect(transaction1.contexts?.trace?.parent_span_id).toBeUndefined(); | ||
expect(transaction2.contexts?.trace?.parent_span_id).toBeUndefined(); | ||
}, | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
Sentry.getCurrentScope().setPropagationContext({ | ||
parentSpanId: '1234567890123456', | ||
spanId: '123456789012345x', | ||
traceId: '12345678901234567890123456789012', | ||
}); | ||
|
||
Sentry.startSpan({ name: 'test_span_1' }, () => undefined); | ||
Sentry.startSpan({ name: 'test_span_2' }, () => undefined); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { expect } from '@playwright/test'; | ||
|
||
import type { TransactionEvent } from '@sentry/types'; | ||
import { sentryTest } from '../../../../utils/fixtures'; | ||
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; | ||
|
||
sentryTest( | ||
'should send manually started parallel root spans in root context with parentSpanId', | ||
async ({ getLocalTestUrl, page }) => { | ||
if (shouldSkipTracingTest()) { | ||
sentryTest.skip(); | ||
} | ||
|
||
const url = await getLocalTestUrl({ testDir: __dirname }); | ||
|
||
const transaction1ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_1'); | ||
const transaction2ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_2'); | ||
|
||
await page.goto(url); | ||
|
||
const [transaction1Req, transaction2Req] = await Promise.all([transaction1ReqPromise, transaction2ReqPromise]); | ||
|
||
const transaction1 = envelopeRequestParser<TransactionEvent>(transaction1Req); | ||
const transaction2 = envelopeRequestParser<TransactionEvent>(transaction2Req); | ||
|
||
expect(transaction1).toBeDefined(); | ||
expect(transaction2).toBeDefined(); | ||
|
||
const trace1Id = transaction1.contexts?.trace?.trace_id; | ||
const trace2Id = transaction2.contexts?.trace?.trace_id; | ||
|
||
expect(trace1Id).toBe('12345678901234567890123456789012'); | ||
expect(trace2Id).toBe('12345678901234567890123456789012'); | ||
|
||
expect(transaction1.contexts?.trace?.parent_span_id).toBe('1234567890123456'); | ||
expect(transaction2.contexts?.trace?.parent_span_id).toBe('1234567890123456'); | ||
}, | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Sentry.startSpan({ name: 'test_span_1' }, () => undefined); | ||
Sentry.startSpan({ name: 'test_span_2' }, () => undefined); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { expect } from '@playwright/test'; | ||
|
||
import type { TransactionEvent } from '@sentry/types'; | ||
import { sentryTest } from '../../../../utils/fixtures'; | ||
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; | ||
|
||
sentryTest('should send manually started parallel root spans in root context', async ({ getLocalTestUrl, page }) => { | ||
if (shouldSkipTracingTest()) { | ||
sentryTest.skip(); | ||
} | ||
|
||
const url = await getLocalTestUrl({ testDir: __dirname }); | ||
|
||
const transaction1ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_1'); | ||
const transaction2ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_2'); | ||
|
||
await page.goto(url); | ||
|
||
const [transaction1Req, transaction2Req] = await Promise.all([transaction1ReqPromise, transaction2ReqPromise]); | ||
|
||
const transaction1 = envelopeRequestParser<TransactionEvent>(transaction1Req); | ||
const transaction2 = envelopeRequestParser<TransactionEvent>(transaction2Req); | ||
|
||
expect(transaction1).toBeDefined(); | ||
expect(transaction2).toBeDefined(); | ||
|
||
const trace1Id = transaction1.contexts?.trace?.trace_id; | ||
const trace2Id = transaction2.contexts?.trace?.trace_id; | ||
|
||
expect(trace1Id).toBeDefined(); | ||
expect(trace2Id).toBeDefined(); | ||
|
||
// We use the same traceID from the root propagation context here | ||
expect(trace1Id).toBe(trace2Id); | ||
|
||
expect(transaction1.contexts?.trace?.parent_span_id).toBeUndefined(); | ||
expect(transaction2.contexts?.trace?.parent_span_id).toBeUndefined(); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { loggingTransport } from '@sentry-internal/node-integration-tests'; | ||
import * as Sentry from '@sentry/node'; | ||
|
||
Sentry.init({ | ||
dsn: 'https://[email protected]/1337', | ||
release: '1.0', | ||
tracesSampleRate: 1.0, | ||
transport: loggingTransport, | ||
}); | ||
|
||
Sentry.getCurrentScope().setPropagationContext({ | ||
parentSpanId: '1234567890123456', | ||
spanId: '123456789012345x', | ||
traceId: '12345678901234567890123456789012', | ||
}); | ||
|
||
const spanIdTraceId = Sentry.startSpan( | ||
{ | ||
name: 'test_span_1', | ||
}, | ||
span1 => span1.spanContext().traceId, | ||
); | ||
|
||
Sentry.startSpan( | ||
{ | ||
name: 'test_span_2', | ||
attributes: { spanIdTraceId }, | ||
}, | ||
() => undefined, | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; | ||
|
||
afterAll(() => { | ||
cleanupChildProcesses(); | ||
}); | ||
|
||
test('should send manually started parallel root spans in root context', done => { | ||
expect.assertions(7); | ||
|
||
createRunner(__dirname, 'scenario.ts') | ||
.expect({ transaction: { transaction: 'test_span_1' } }) | ||
.expect({ | ||
transaction: transaction => { | ||
expect(transaction).toBeDefined(); | ||
const traceId = transaction.contexts?.trace?.trace_id; | ||
expect(traceId).toBeDefined(); | ||
|
||
// It ignores propagation context of the root context | ||
expect(traceId).not.toBe('12345678901234567890123456789012'); | ||
expect(transaction.contexts?.trace?.parent_span_id).toBeUndefined(); | ||
|
||
// Different trace ID than the first span | ||
const trace1Id = transaction.contexts?.trace?.data?.spanIdTraceId; | ||
expect(trace1Id).toBeDefined(); | ||
expect(trace1Id).not.toBe(traceId); | ||
}, | ||
}) | ||
.start(done); | ||
}); |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. okay so if we're within the |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { loggingTransport } from '@sentry-internal/node-integration-tests'; | ||
import * as Sentry from '@sentry/node'; | ||
|
||
Sentry.init({ | ||
dsn: 'https://[email protected]/1337', | ||
release: '1.0', | ||
tracesSampleRate: 1.0, | ||
transport: loggingTransport, | ||
}); | ||
|
||
Sentry.withScope(scope => { | ||
scope.setPropagationContext({ | ||
parentSpanId: '1234567890123456', | ||
spanId: '123456789012345x', | ||
traceId: '12345678901234567890123456789012', | ||
}); | ||
|
||
Sentry.startSpan({ name: 'test_span_1' }, () => undefined); | ||
Sentry.startSpan({ name: 'test_span_2' }, () => undefined); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; | ||
|
||
afterAll(() => { | ||
cleanupChildProcesses(); | ||
}); | ||
|
||
test('should send manually started parallel root spans outside of root context with parentSpanId', done => { | ||
createRunner(__dirname, 'scenario.ts') | ||
.expect({ | ||
transaction: { | ||
transaction: 'test_span_1', | ||
contexts: { | ||
trace: { | ||
span_id: expect.any(String), | ||
parent_span_id: '1234567890123456', | ||
trace_id: '12345678901234567890123456789012', | ||
}, | ||
}, | ||
}, | ||
}) | ||
.expect({ | ||
transaction: { | ||
transaction: 'test_span_2', | ||
contexts: { | ||
trace: { | ||
span_id: expect.any(String), | ||
parent_span_id: '1234567890123456', | ||
trace_id: '12345678901234567890123456789012', | ||
}, | ||
}, | ||
}, | ||
}) | ||
.start(done); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { loggingTransport } from '@sentry-internal/node-integration-tests'; | ||
import * as Sentry from '@sentry/node'; | ||
|
||
Sentry.init({ | ||
dsn: 'https://[email protected]/1337', | ||
release: '1.0', | ||
tracesSampleRate: 1.0, | ||
transport: loggingTransport, | ||
}); | ||
|
||
Sentry.withScope(() => { | ||
const spanIdTraceId = Sentry.startSpan( | ||
{ | ||
name: 'test_span_1', | ||
}, | ||
span1 => span1.spanContext().traceId, | ||
); | ||
|
||
Sentry.startSpan( | ||
{ | ||
name: 'test_span_2', | ||
attributes: { spanIdTraceId }, | ||
}, | ||
() => undefined, | ||
); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; | ||
|
||
afterAll(() => { | ||
cleanupChildProcesses(); | ||
}); | ||
|
||
test('should send manually started parallel root spans outside of root context', done => { | ||
expect.assertions(6); | ||
|
||
createRunner(__dirname, 'scenario.ts') | ||
.expect({ transaction: { transaction: 'test_span_1' } }) | ||
.expect({ | ||
transaction: transaction => { | ||
expect(transaction).toBeDefined(); | ||
const traceId = transaction.contexts?.trace?.trace_id; | ||
expect(traceId).toBeDefined(); | ||
expect(transaction.contexts?.trace?.parent_span_id).toBeUndefined(); | ||
|
||
const trace1Id = transaction.contexts?.trace?.data?.spanIdTraceId; | ||
expect(trace1Id).toBeDefined(); | ||
|
||
// Same trace ID as the first span | ||
expect(trace1Id).toBe(traceId); | ||
}, | ||
}) | ||
.start(done); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -176,8 +176,9 @@ function ensureTimestampInMilliseconds(timestamp: number): number { | |
|
||
function getContext(scope: Scope | undefined, forceTransaction: boolean | undefined): Context { | ||
const ctx = getContextForScope(scope); | ||
// Note: If the context is the ROOT_CONTEXT, no scope is attached | ||
// Thus we will not use the propagation context in this case, which is desired | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ahh I see, this expains the behaviour I wondered about above |
||
const actualScope = getScopesFromContext(ctx)?.scope; | ||
|
||
const parentSpan = trace.getSpan(ctx); | ||
|
||
// In the case that we have no parent span, we need to "simulate" one to ensure the propagation context is correct | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interesting! I would have thought it picks up the trace id from the propagation context, given that there's no request/non-recording span. But good to know!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, as you noted below, this is def. weird behavior but happens because of special stuff that happens around the ROOT_CONTEXT 😅