Skip to content

Commit 5b42422

Browse files
committed
adjust parseSpanDescription and tests
1 parent 2ced45d commit 5b42422

File tree

7 files changed

+226
-43
lines changed

7 files changed

+226
-43
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
const activeSpan = Sentry.getActiveSpan();
22
const rootSpan = activeSpan && Sentry.getRootSpan(activeSpan);
3-
rootSpan.updateName('new name');
3+
rootSpan?.updateName('new name');

dev-packages/node-integration-tests/suites/express/tracing/updateName/server.js

+8
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ app.get('/test/:id/updateSpanName', (_req, res) => {
4545
res.send({ response: 'response 3' });
4646
});
4747

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+
4856
Sentry.setupExpressErrorHandler(app);
4957

5058
startExpressServerAndSendPortToRunner(app);

dev-packages/node-integration-tests/suites/express/tracing/updateName/test.ts

+26
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ describe('express tracing', () => {
5252
},
5353
contexts: {
5454
trace: {
55+
op: 'http.server',
5556
data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom' },
5657
},
5758
},
@@ -64,4 +65,29 @@ describe('express tracing', () => {
6465
.makeRequest('get', '/test/123/updateSpanName');
6566
});
6667
});
68+
69+
// This test documents the correct way to update the span name (and implicitly the source) in Node:
70+
test('calling `Sentry.updateSpanName` and setting source subsequently updates the final name and sets correct source', done => {
71+
createRunner(__dirname, 'server.js')
72+
.expect({
73+
transaction: txnEvent => {
74+
expect(txnEvent).toMatchObject({
75+
transaction: 'new-name',
76+
transaction_info: {
77+
source: 'component',
78+
},
79+
contexts: {
80+
trace: {
81+
op: 'http.server',
82+
data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component' },
83+
},
84+
},
85+
});
86+
// ensure we delete the internal attribute once we're done with it
87+
expect(txnEvent.contexts?.trace?.data?.['_sentry_span_name_set_by_user']).toBeUndefined();
88+
},
89+
})
90+
.start(done)
91+
.makeRequest('get', '/test/123/updateSpanNameAndSource');
92+
});
6793
});

dev-packages/node-integration-tests/suites/public-api/startSpan/basic-usage/test.ts

+18
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,21 @@ test('sends a manually started root span with source custom', done => {
2222
})
2323
.start(done);
2424
});
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+
})
41+
.start(done);
42+
});

packages/core/src/utils/spanUtils.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@ export function showSpanDropWarning(): void {
333333
*/
334334
export function updateSpanName(span: Span, name: string): void {
335335
span.updateName(name);
336-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'custom');
337-
span.setAttribute('_sentry_span_name_set_by_user', name);
336+
span.setAttributes({
337+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
338+
['_sentry_span_name_set_by_user']: name,
339+
});
338340
}

packages/opentelemetry/src/utils/parseSpanDescription.ts

+59-25
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function inferSpanData(spanName: string, attributes: SpanAttributes, kind
4242
// eslint-disable-next-line deprecation/deprecation
4343
const httpMethod = attributes[ATTR_HTTP_REQUEST_METHOD] || attributes[SEMATTRS_HTTP_METHOD];
4444
if (httpMethod) {
45-
return descriptionForHttpMethod({ attributes, name: getOriginalName(spanName, attributes), kind }, httpMethod);
45+
return descriptionForHttpMethod({ attributes, name: spanName, kind }, httpMethod);
4646
}
4747

4848
// eslint-disable-next-line deprecation/deprecation
@@ -64,9 +64,8 @@ export function inferSpanData(spanName: string, attributes: SpanAttributes, kind
6464
const rpcService = attributes[SEMATTRS_RPC_SERVICE];
6565
if (rpcService) {
6666
return {
67+
...getUserUpdatedNameAndSource(spanName, attributes, 'route'),
6768
op: 'rpc',
68-
description: getOriginalName(spanName, attributes),
69-
source: customSourceOrRoute,
7069
};
7170
}
7271

@@ -75,9 +74,8 @@ export function inferSpanData(spanName: string, attributes: SpanAttributes, kind
7574
const messagingSystem = attributes[SEMATTRS_MESSAGING_SYSTEM];
7675
if (messagingSystem) {
7776
return {
77+
...getUserUpdatedNameAndSource(spanName, attributes, customSourceOrRoute),
7878
op: 'message',
79-
description: getOriginalName(spanName, attributes),
80-
source: customSourceOrRoute,
8179
};
8280
}
8381

@@ -86,9 +84,8 @@ export function inferSpanData(spanName: string, attributes: SpanAttributes, kind
8684
const faasTrigger = attributes[SEMATTRS_FAAS_TRIGGER];
8785
if (faasTrigger) {
8886
return {
87+
...getUserUpdatedNameAndSource(spanName, attributes, customSourceOrRoute),
8988
op: faasTrigger.toString(),
90-
description: getOriginalName(spanName, attributes),
91-
source: customSourceOrRoute,
9289
};
9390
}
9491

@@ -108,14 +105,27 @@ export function parseSpanDescription(span: AbstractSpan): SpanDescription {
108105
const attributes = spanHasAttributes(span) ? span.attributes : {};
109106
const name = spanHasName(span) ? span.name : '<unknown>';
110107
const kind = getSpanKind(span);
108+
// console.log('x parseSpanDesc', { attributes, name, kind });
111109

112-
return inferSpanData(name, attributes, kind);
110+
const res = inferSpanData(name, attributes, kind);
111+
112+
// console.log('x parseSpanDesc res', res);
113+
return res;
113114
}
114115

115116
function descriptionForDbSystem({ attributes, name }: { attributes: Attributes; name: string }): SpanDescription {
116-
// if we already set the source to custom, we don't overwrite the span description but just adjust the op
117+
// if we already have a custom name, we don't overwrite it but only set the op
118+
if (typeof attributes['_sentry_span_name_set_by_user'] === 'string') {
119+
return {
120+
op: 'db',
121+
description: attributes['_sentry_span_name_set_by_user'],
122+
source: (attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] as TransactionSource) || 'custom',
123+
};
124+
}
125+
126+
// if we already have the source set to custom, we don't overwrite the span description but only set the op
117127
if (attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] === 'custom') {
118-
return { op: 'db', description: getOriginalName(name, attributes), source: 'custom' };
128+
return { op: 'db', description: name, source: 'custom' };
119129
}
120130

121131
// Use DB statement (Ex "SELECT * FROM table") if possible as description.
@@ -151,7 +161,7 @@ export function descriptionForHttpMethod(
151161
const { urlPath, url, query, fragment, hasRoute } = getSanitizedUrl(attributes, kind);
152162

153163
if (!urlPath) {
154-
return { op: opParts.join('.'), description: getOriginalName(name, attributes), source: 'custom' };
164+
return { ...getUserUpdatedNameAndSource(name, attributes), op: opParts.join('.') };
155165
}
156166

157167
const graphqlOperationsAttribute = attributes[SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION];
@@ -161,12 +171,12 @@ export function descriptionForHttpMethod(
161171

162172
// When the http span has a graphql operation, append it to the description
163173
// We add these in the graphqlIntegration
164-
const description = graphqlOperationsAttribute
174+
const inferredDescription = graphqlOperationsAttribute
165175
? `${baseDescription} (${getGraphqlOperationNamesFromAttribute(graphqlOperationsAttribute)})`
166176
: baseDescription;
167177

168178
// If `httpPath` is a root path, then we can categorize the transaction source as route.
169-
const source: TransactionSource = hasRoute || urlPath === '/' ? 'route' : 'url';
179+
const inferredSource: TransactionSource = hasRoute || urlPath === '/' ? 'route' : 'url';
170180

171181
const data: Record<string, string> = {};
172182

@@ -190,15 +200,22 @@ export function descriptionForHttpMethod(
190200
const origin = attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] || 'manual';
191201
const isManualSpan = !`${origin}`.startsWith('auto');
192202

193-
// If users (or in very rare occasions we) set the source to custom, we don't overwrite it
203+
// If users (or in very rare occasions we) set the source to custom, we don't overwrite the name
194204
const alreadyHasCustomSource = attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] === 'custom';
195205

196-
const useInferredDescription = !alreadyHasCustomSource && (isClientOrServerKind || !isManualSpan);
206+
const useInferredDescription =
207+
!alreadyHasCustomSource &&
208+
attributes['_sentry_span_name_set_by_user'] == null &&
209+
(isClientOrServerKind || !isManualSpan);
210+
211+
const { description, source } = useInferredDescription
212+
? { description: inferredDescription, source: inferredSource }
213+
: getUserUpdatedNameAndSource(name, attributes);
197214

198215
return {
199216
op: opParts.join('.'),
200-
description: useInferredDescription ? description : getOriginalName(name, attributes),
201-
source: useInferredDescription ? source : 'custom',
217+
description,
218+
source,
202219
data,
203220
};
204221
}
@@ -265,15 +282,32 @@ export function getSanitizedUrl(
265282
}
266283

267284
/**
268-
* Because Otel decided to mutate span names via `span.updateName`, the only way to ensure
269-
* that a user-set span name is preserved is to store it as a tmp attribute on the span.
285+
* Because Otel instrumentation sometimes mutates span names via `span.updateName`, the only way
286+
* to ensure that a user-set span name is preserved is to store it as a tmp attribute on the span.
270287
* We delete this attribute once we're done with it when preparing the event envelope.
288+
*
289+
* This temp attribute always takes precedence over the original name.
290+
*
291+
* We also need to take care of setting the correct source. Users can always update the source
292+
* after updating the name, so we need to respect that.
293+
*
271294
* @internal exported only for testing
272295
*/
273-
export function getOriginalName(name: string, attributes: Attributes): string {
274-
return attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] === 'custom' &&
275-
attributes['_sentry_span_name_set_by_user'] &&
276-
typeof attributes['_sentry_span_name_set_by_user'] === 'string'
277-
? attributes['_sentry_span_name_set_by_user']
278-
: name;
296+
export function getUserUpdatedNameAndSource(
297+
originalName: string,
298+
attributes: Attributes,
299+
fallbackSource: TransactionSource = 'custom',
300+
): {
301+
description: string;
302+
source: TransactionSource;
303+
} {
304+
const source = (attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] as TransactionSource) || fallbackSource;
305+
306+
if (attributes['_sentry_span_name_set_by_user'] && typeof attributes['_sentry_span_name_set_by_user'] === 'string')
307+
return {
308+
description: attributes['_sentry_span_name_set_by_user'],
309+
source,
310+
};
311+
312+
return { description: originalName, source };
279313
}

0 commit comments

Comments
 (0)