Skip to content

Commit 3a7743c

Browse files
authored
feat(core): Add updateSpanName helper function [v8] (#14736)
Backport of #14291 to v8
1 parent e80eefe commit 3a7743c

File tree

32 files changed

+830
-40
lines changed

32 files changed

+830
-40
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect } from '@playwright/test';
2-
import type { Event } from '@sentry/core';
2+
import { type Event, SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME } from '@sentry/core';
33

44
import {
55
SEMANTIC_ATTRIBUTE_SENTRY_OP,
@@ -10,27 +10,34 @@ import {
1010
import { sentryTest } from '../../../../utils/fixtures';
1111
import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers';
1212

13-
sentryTest('sets the source to custom when updating the transaction name', async ({ getLocalTestUrl, page }) => {
14-
if (shouldSkipTracingTest()) {
15-
sentryTest.skip();
16-
}
13+
sentryTest(
14+
'sets the source to custom when updating the transaction name with `span.updateName`',
15+
async ({ getLocalTestUrl, page }) => {
16+
if (shouldSkipTracingTest()) {
17+
sentryTest.skip();
18+
}
1719

18-
const url = await getLocalTestUrl({ testDir: __dirname });
20+
const url = await getLocalTestUrl({ testDir: __dirname });
1921

20-
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
22+
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
2123

22-
const traceContextData = eventData.contexts?.trace?.data;
24+
const traceContextData = eventData.contexts?.trace?.data;
2325

24-
expect(traceContextData).toMatchObject({
25-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.browser',
26-
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
27-
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
28-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
29-
});
26+
expect(traceContextData).toBeDefined();
3027

31-
expect(eventData.transaction).toBe('new name');
28+
expect(eventData.transaction).toBe('new name');
3229

33-
expect(eventData.contexts?.trace?.op).toBe('pageload');
34-
expect(eventData.spans?.length).toBeGreaterThan(0);
35-
expect(eventData.transaction_info?.source).toEqual('custom');
36-
});
30+
expect(traceContextData).toMatchObject({
31+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.browser',
32+
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
33+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
34+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
35+
});
36+
37+
expect(traceContextData![SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]).toBeUndefined();
38+
39+
expect(eventData.contexts?.trace?.op).toBe('pageload');
40+
expect(eventData.spans?.length).toBeGreaterThan(0);
41+
expect(eventData.transaction_info?.source).toEqual('custom');
42+
},
43+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window._testBaseTimestamp = performance.timeOrigin / 1000;
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
integrations: [Sentry.browserTracingIntegration()],
9+
tracesSampleRate: 1,
10+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const activeSpan = Sentry.getActiveSpan();
2+
const rootSpan = activeSpan && Sentry.getRootSpan(activeSpan);
3+
4+
Sentry.updateSpanName(rootSpan, 'new name');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { expect } from '@playwright/test';
2+
import { type Event, SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME } from '@sentry/core';
3+
4+
import {
5+
SEMANTIC_ATTRIBUTE_SENTRY_OP,
6+
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
7+
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
8+
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
9+
} from '@sentry/browser';
10+
import { sentryTest } from '../../../../utils/fixtures';
11+
import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers';
12+
13+
sentryTest(
14+
'sets the source to custom when updating the transaction name with Sentry.updateSpanName',
15+
async ({ getLocalTestUrl, page }) => {
16+
if (shouldSkipTracingTest()) {
17+
sentryTest.skip();
18+
}
19+
20+
const url = await getLocalTestUrl({ testDir: __dirname });
21+
22+
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
23+
24+
const traceContextData = eventData.contexts?.trace?.data;
25+
26+
expect(traceContextData).toBeDefined();
27+
28+
expect(traceContextData).toMatchObject({
29+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.browser',
30+
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
31+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
32+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
33+
});
34+
35+
expect(traceContextData![SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]).toBeUndefined();
36+
37+
expect(eventData.transaction).toBe('new name');
38+
39+
expect(eventData.contexts?.trace?.op).toBe('pageload');
40+
expect(eventData.spans?.length).toBeGreaterThan(0);
41+
expect(eventData.transaction_info?.source).toEqual('custom');
42+
},
43+
);

dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/test.ts

+4
Original file line numberDiff line numberDiff line change
@@ -181,5 +181,9 @@ async function captureErrorAndGetEnvelopeTraceHeader(page: Page): Promise<Partia
181181
await page.locator('#btnCaptureError').click();
182182

183183
const [, errorEnvelopeTraceHeader] = (await errorEventPromise)[0];
184+
185+
// @ts-expect-error - EventEnvelopeHeaders type in (types/envelope.ts) suggests that trace_id is optional,
186+
// which the DynamicSamplingContext type does not permit.
187+
// TODO(v9): We should adjust the EventEnvelopeHeaders type because the trace header always needs to have a trace_id
184188
return errorEnvelopeTraceHeader;
185189
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
2+
const Sentry = require('@sentry/node');
3+
4+
Sentry.init({
5+
dsn: 'https://[email protected]/1337',
6+
release: '1.0',
7+
// disable attaching headers to /test/* endpoints
8+
tracePropagationTargets: [/^(?!.*test).*$/],
9+
tracesSampleRate: 1.0,
10+
transport: loggingTransport,
11+
});
12+
13+
// express must be required after Sentry is initialized
14+
const express = require('express');
15+
const cors = require('cors');
16+
const bodyParser = require('body-parser');
17+
const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests');
18+
19+
const app = express();
20+
21+
app.use(cors());
22+
app.use(bodyParser.json());
23+
app.use(bodyParser.text());
24+
app.use(bodyParser.raw());
25+
26+
app.get('/test/:id/span-updateName', (_req, res) => {
27+
const span = Sentry.getActiveSpan();
28+
const rootSpan = Sentry.getRootSpan(span);
29+
rootSpan.updateName('new-name');
30+
res.send({ response: 'response 1' });
31+
});
32+
33+
app.get('/test/:id/span-updateName-source', (_req, res) => {
34+
const span = Sentry.getActiveSpan();
35+
const rootSpan = Sentry.getRootSpan(span);
36+
rootSpan.updateName('new-name');
37+
rootSpan.setAttribute(Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'custom');
38+
res.send({ response: 'response 2' });
39+
});
40+
41+
app.get('/test/:id/updateSpanName', (_req, res) => {
42+
const span = Sentry.getActiveSpan();
43+
const rootSpan = Sentry.getRootSpan(span);
44+
Sentry.updateSpanName(rootSpan, 'new-name');
45+
res.send({ response: 'response 3' });
46+
});
47+
48+
app.get('/test/:id/updateSpanNameAndSource', (_req, res) => {
49+
const span = Sentry.getActiveSpan();
50+
const rootSpan = Sentry.getRootSpan(span);
51+
Sentry.updateSpanName(rootSpan, 'new-name');
52+
rootSpan.setAttribute(Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'component');
53+
res.send({ response: 'response 4' });
54+
});
55+
56+
Sentry.setupExpressErrorHandler(app);
57+
58+
startExpressServerAndSendPortToRunner(app);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME } from '@sentry/core';
2+
import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/node';
3+
import { cleanupChildProcesses, createRunner } from '../../../../utils/runner';
4+
5+
describe('express tracing', () => {
6+
afterAll(() => {
7+
cleanupChildProcesses();
8+
});
9+
10+
describe('CJS', () => {
11+
// This test documents the unfortunate behaviour of using `span.updateName` on the server-side.
12+
// For http.server root spans (which is the root span on the server 99% of the time), Otel's http instrumentation
13+
// calls `span.updateName` and overwrites whatever the name was set to before (by us or by users).
14+
test("calling just `span.updateName` doesn't update the final name in express (missing source)", done => {
15+
createRunner(__dirname, 'server.js')
16+
.expect({
17+
transaction: {
18+
transaction: 'GET /test/:id/span-updateName',
19+
transaction_info: {
20+
source: 'route',
21+
},
22+
},
23+
})
24+
.start(done)
25+
.makeRequest('get', '/test/123/span-updateName');
26+
});
27+
28+
// Also calling `updateName` AND setting a source doesn't change anything - Otel has no concept of source, this is sentry-internal.
29+
// Therefore, only the source is updated but the name is still overwritten by Otel.
30+
test("calling `span.updateName` and setting attribute source doesn't update the final name in express but it updates the source", done => {
31+
createRunner(__dirname, 'server.js')
32+
.expect({
33+
transaction: {
34+
transaction: 'GET /test/:id/span-updateName-source',
35+
transaction_info: {
36+
source: 'custom',
37+
},
38+
},
39+
})
40+
.start(done)
41+
.makeRequest('get', '/test/123/span-updateName-source');
42+
});
43+
44+
// This test documents the correct way to update the span name (and implicitly the source) in Node:
45+
test('calling `Sentry.updateSpanName` updates the final name and source in express', done => {
46+
createRunner(__dirname, 'server.js')
47+
.expect({
48+
transaction: txnEvent => {
49+
expect(txnEvent).toMatchObject({
50+
transaction: 'new-name',
51+
transaction_info: {
52+
source: 'custom',
53+
},
54+
contexts: {
55+
trace: {
56+
op: 'http.server',
57+
data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom' },
58+
},
59+
},
60+
});
61+
// ensure we delete the internal attribute once we're done with it
62+
expect(txnEvent.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]).toBeUndefined();
63+
},
64+
})
65+
.start(done)
66+
.makeRequest('get', '/test/123/updateSpanName');
67+
});
68+
});
69+
70+
// This test documents the correct way to update the span name (and implicitly the source) in Node:
71+
test('calling `Sentry.updateSpanName` and setting source subsequently updates the final name and sets correct source', done => {
72+
createRunner(__dirname, 'server.js')
73+
.expect({
74+
transaction: txnEvent => {
75+
expect(txnEvent).toMatchObject({
76+
transaction: 'new-name',
77+
transaction_info: {
78+
source: 'component',
79+
},
80+
contexts: {
81+
trace: {
82+
op: 'http.server',
83+
data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component' },
84+
},
85+
},
86+
});
87+
// ensure we delete the internal attribute once we're done with it
88+
expect(txnEvent.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME]).toBeUndefined();
89+
},
90+
})
91+
.start(done)
92+
.makeRequest('get', '/test/123/updateSpanNameAndSource');
93+
});
94+
});
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,42 @@
1+
import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/node';
12
import { cleanupChildProcesses, createRunner } from '../../../../utils/runner';
23

34
afterAll(() => {
45
cleanupChildProcesses();
56
});
67

7-
test('should send a manually started root span', done => {
8+
test('sends a manually started root span with source custom', done => {
89
createRunner(__dirname, 'scenario.ts')
9-
.expect({ transaction: { transaction: 'test_span' } })
10+
.expect({
11+
transaction: {
12+
transaction: 'test_span',
13+
transaction_info: { source: 'custom' },
14+
contexts: {
15+
trace: {
16+
span_id: expect.any(String),
17+
trace_id: expect.any(String),
18+
data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom' },
19+
},
20+
},
21+
},
22+
})
23+
.start(done);
24+
});
25+
26+
test("doesn't change the name for manually started spans even if attributes triggering inference are set", done => {
27+
createRunner(__dirname, 'scenario.ts')
28+
.expect({
29+
transaction: {
30+
transaction: 'test_span',
31+
transaction_info: { source: 'custom' },
32+
contexts: {
33+
trace: {
34+
span_id: expect.any(String),
35+
trace_id: expect.any(String),
36+
data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom' },
37+
},
38+
},
39+
},
40+
})
1041
.start(done);
1142
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
2+
import * as Sentry from '@sentry/node';
3+
4+
Sentry.init({
5+
dsn: 'https://[email protected]/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
transport: loggingTransport,
9+
});
10+
11+
Sentry.startSpan(
12+
{ name: 'test_span', attributes: { [Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url' } },
13+
(span: Sentry.Span) => {
14+
span.updateName('new name');
15+
},
16+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/node';
2+
import { cleanupChildProcesses, createRunner } from '../../../../utils/runner';
3+
4+
afterAll(() => {
5+
cleanupChildProcesses();
6+
});
7+
8+
test('updates the span name when calling `span.updateName`', done => {
9+
createRunner(__dirname, 'scenario.ts')
10+
.expect({
11+
transaction: {
12+
transaction: 'new name',
13+
transaction_info: { source: 'url' },
14+
contexts: {
15+
trace: {
16+
span_id: expect.any(String),
17+
trace_id: expect.any(String),
18+
data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url' },
19+
},
20+
},
21+
},
22+
})
23+
.start(done);
24+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
2+
import * as Sentry from '@sentry/node';
3+
4+
Sentry.init({
5+
dsn: 'https://[email protected]/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
transport: loggingTransport,
9+
});
10+
11+
Sentry.startSpan(
12+
{ name: 'test_span', attributes: { [Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url' } },
13+
(span: Sentry.Span) => {
14+
Sentry.updateSpanName(span, 'new name');
15+
},
16+
);

0 commit comments

Comments
 (0)