Skip to content

Commit 32d395d

Browse files
committed
adjust parseSpanDescription and tests
1 parent 2ed0509 commit 32d395d

File tree

8 files changed

+230
-44
lines changed

8 files changed

+230
-44
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
@@ -306,6 +306,8 @@ export function updateMetricSummaryOnActiveSpan(
306306
*/
307307
export function updateSpanName(span: Span, name: string): void {
308308
span.updateName(name);
309-
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'custom');
310-
span.setAttribute('_sentry_span_name_set_by_user', name);
309+
span.setAttributes({
310+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
311+
['_sentry_span_name_set_by_user']: name,
312+
});
311313
}

packages/nestjs/src/decorators/sentry-traced.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { startSpan } from '@sentry/node';
1+
import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan } from '@sentry/node';
22

33
/**
44
* A decorator usable to wrap arbitrary functions with spans.
@@ -14,6 +14,9 @@ export function SentryTraced(op: string = 'function') {
1414
{
1515
op: op,
1616
name: propertyKey,
17+
attributes: {
18+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component',
19+
},
1720
},
1821
() => {
1922
return originalMethod.apply(this, args);

packages/opentelemetry/src/utils/parseSpanDescription.ts

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

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

@@ -74,9 +73,8 @@ export function inferSpanData(spanName: string, attributes: SpanAttributes, kind
7473
const messagingSystem = attributes[SEMATTRS_MESSAGING_SYSTEM];
7574
if (messagingSystem) {
7675
return {
76+
...getUserUpdatedNameAndSource(spanName, attributes, customSourceOrRoute),
7777
op: 'message',
78-
description: getOriginalName(spanName, attributes),
79-
source: customSourceOrRoute,
8078
};
8179
}
8280

@@ -85,9 +83,8 @@ export function inferSpanData(spanName: string, attributes: SpanAttributes, kind
8583
const faasTrigger = attributes[SEMATTRS_FAAS_TRIGGER];
8684
if (faasTrigger) {
8785
return {
86+
...getUserUpdatedNameAndSource(spanName, attributes, customSourceOrRoute),
8887
op: faasTrigger.toString(),
89-
description: getOriginalName(spanName, attributes),
90-
source: customSourceOrRoute,
9188
};
9289
}
9390

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

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

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

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

152162
if (!urlPath) {
153-
return { op: opParts.join('.'), description: getOriginalName(name, attributes), source: 'custom' };
163+
return { ...getUserUpdatedNameAndSource(name, attributes), op: opParts.join('.') };
154164
}
155165

156166
const graphqlOperationsAttribute = attributes[SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION];
@@ -160,12 +170,12 @@ export function descriptionForHttpMethod(
160170

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

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

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

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

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

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

197214
return {
198215
op: opParts.join('.'),
199-
description: useInferredDescription ? description : getOriginalName(name, attributes),
200-
source: useInferredDescription ? source : 'custom',
216+
description,
217+
source,
201218
data,
202219
};
203220
}
@@ -264,15 +281,32 @@ export function getSanitizedUrl(
264281
}
265282

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

0 commit comments

Comments
 (0)