Skip to content

Commit 2981b85

Browse files
authored
ref(tracing): Remove span.startChild() (#11376)
Also refactor the span logging behavior into a logging integration, to make this consistent between core & otel-powered spans.
1 parent 14c7c6b commit 2981b85

File tree

13 files changed

+127
-158
lines changed

13 files changed

+127
-158
lines changed

packages/browser/src/integrations/breadcrumbs.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable max-lines */
21
import { addBreadcrumb, defineIntegration, getClient } from '@sentry/core';
32
import type {
43
Client,

packages/core/src/tracing/idleSpan.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,6 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
196196
function _pushActivity(spanId: string): void {
197197
_cancelIdleTimeout();
198198
activities.set(spanId, true);
199-
DEBUG_BUILD && logger.log(`[Tracing] pushActivity: ${spanId}`);
200-
DEBUG_BUILD && logger.log('[Tracing] new activities count', activities.size);
201199

202200
const endTimestamp = timestampInSeconds();
203201
// We need to add the timeout here to have the real endtimestamp of the idle span
@@ -211,9 +209,7 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
211209
*/
212210
function _popActivity(spanId: string): void {
213211
if (activities.has(spanId)) {
214-
DEBUG_BUILD && logger.log(`[Tracing] popActivity ${spanId}`);
215212
activities.delete(spanId);
216-
DEBUG_BUILD && logger.log('[Tracing] new activities count', activities.size);
217213
}
218214

219215
if (activities.size === 0) {
@@ -248,8 +244,7 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
248244
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON, _finishReason);
249245
}
250246

251-
DEBUG_BUILD &&
252-
logger.log('[Tracing] finishing idle span', new Date(endTimestamp * 1000).toISOString(), spanJSON.op);
247+
logger.log(`[Tracing] Idle span "${spanJSON.op}" finished`);
253248

254249
const childSpans = getSpanDescendants(span).filter(child => child !== span);
255250

@@ -259,7 +254,7 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
259254
childSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'cancelled' });
260255
childSpan.end(endTimestamp);
261256
DEBUG_BUILD &&
262-
logger.log('[Tracing] cancelling span since span ended early', JSON.stringify(childSpan, undefined, 2));
257+
logger.log('[Tracing] Cancelling span since span ended early', JSON.stringify(childSpan, undefined, 2));
263258
}
264259

265260
const childSpanJSON = spanToJSON(childSpan);
@@ -274,18 +269,16 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti
274269
if (DEBUG_BUILD) {
275270
const stringifiedSpan = JSON.stringify(childSpan, undefined, 2);
276271
if (!spanStartedBeforeIdleSpanEnd) {
277-
logger.log('[Tracing] discarding Span since it happened after idle span was finished', stringifiedSpan);
272+
logger.log('[Tracing] Discarding span since it happened after idle span was finished', stringifiedSpan);
278273
} else if (!spanEndedBeforeFinalTimeout) {
279-
logger.log('[Tracing] discarding Span since it finished after idle span final timeout', stringifiedSpan);
274+
logger.log('[Tracing] Discarding span since it finished after idle span final timeout', stringifiedSpan);
280275
}
281276
}
282277

283278
if (!spanEndedBeforeFinalTimeout || !spanStartedBeforeIdleSpanEnd) {
284279
removeChildSpanFromSpan(span, childSpan);
285280
}
286281
});
287-
288-
DEBUG_BUILD && logger.log('[Tracing] flushing idle span');
289282
}
290283

291284
client.on('spanStart', startedSpan => {
@@ -349,7 +342,7 @@ function _startIdleSpan(options: StartSpanOptions): Span {
349342

350343
_setSpanForScope(getCurrentScope(), span);
351344

352-
DEBUG_BUILD && logger.log(`Setting idle span on scope. Span ID: ${span.spanContext().spanId}`);
345+
DEBUG_BUILD && logger.log('[Tracing] Started span is an idle span');
353346

354347
return span;
355348
}

packages/core/src/tracing/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ export {
1818
export { getDynamicSamplingContextFromClient, getDynamicSamplingContextFromSpan } from './dynamicSamplingContext';
1919
export { setMeasurement } from './measurement';
2020
export { sampleSpan } from './sampling';
21+
export { logSpanEnd, logSpanStart } from './logSpans';

packages/core/src/tracing/logSpans.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { Span } from '@sentry/types';
2+
import { logger } from '@sentry/utils';
3+
import { DEBUG_BUILD } from '../debug-build';
4+
import { getRootSpan, spanIsSampled, spanToJSON } from '../utils/spanUtils';
5+
6+
/**
7+
* Print a log message for a started span.
8+
*/
9+
export function logSpanStart(span: Span): void {
10+
if (!DEBUG_BUILD) return;
11+
12+
const { description = '< unknown name >', op = '< unknown op >', parent_span_id: parentSpanId } = spanToJSON(span);
13+
const { spanId } = span.spanContext();
14+
15+
const sampled = spanIsSampled(span);
16+
const rootSpan = getRootSpan(span);
17+
const isRootSpan = rootSpan === span;
18+
19+
const header = `[Tracing] Starting ${sampled ? 'sampled' : 'unsampled'} ${isRootSpan ? 'root ' : ''}span`;
20+
21+
const infoParts: string[] = [`op: ${op}`, `name: ${description}`, `ID: ${spanId}`];
22+
23+
if (parentSpanId) {
24+
infoParts.push(`parent ID: ${parentSpanId}`);
25+
}
26+
27+
if (!isRootSpan) {
28+
const { op, description } = spanToJSON(rootSpan);
29+
infoParts.push(`root ID: ${rootSpan.spanContext().spanId}`);
30+
if (op) {
31+
infoParts.push(`root op: ${op}`);
32+
}
33+
if (description) {
34+
infoParts.push(`root description: ${description}`);
35+
}
36+
}
37+
38+
logger.log(`${header}
39+
${infoParts.join('\n ')}`);
40+
}
41+
42+
/**
43+
* Print a log message for an ended span.
44+
*/
45+
export function logSpanEnd(span: Span): void {
46+
if (!DEBUG_BUILD) return;
47+
48+
const { description = '< unknown name >', op = '< unknown op >' } = spanToJSON(span);
49+
const { spanId } = span.spanContext();
50+
const rootSpan = getRootSpan(span);
51+
const isRootSpan = rootSpan === span;
52+
53+
const msg = `[Tracing] Finishing "${op}" ${isRootSpan ? 'root ' : ''}span "${description}" with ID ${spanId}`;
54+
logger.log(msg);
55+
}

packages/core/src/tracing/sentrySpan.ts

Lines changed: 4 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,13 @@ import type {
99
SpanStatus,
1010
SpanTimeInput,
1111
} from '@sentry/types';
12-
import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils';
12+
import { dropUndefinedKeys, timestampInSeconds, uuid4 } from '@sentry/utils';
1313
import { getClient } from '../currentScopes';
1414

15-
import { DEBUG_BUILD } from '../debug-build';
1615
import { getMetricSummaryJsonForSpan } from '../metrics/metric-summary';
1716
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes';
18-
import {
19-
TRACE_FLAG_NONE,
20-
TRACE_FLAG_SAMPLED,
21-
addChildSpanToSpan,
22-
getRootSpan,
23-
getStatusMessage,
24-
spanTimeInputToSeconds,
25-
spanToJSON,
26-
} from '../utils/spanUtils';
17+
import { TRACE_FLAG_NONE, TRACE_FLAG_SAMPLED, getStatusMessage, spanTimeInputToSeconds } from '../utils/spanUtils';
18+
import { logSpanEnd } from './logSpans';
2719

2820
/**
2921
* Span contains all data about a span
@@ -42,8 +34,6 @@ export class SentrySpan implements Span {
4234
/** Internal keeper of the status */
4335
protected _status?: SpanStatus;
4436

45-
private _logMessage?: string;
46-
4737
/**
4838
* You should never call the constructor manually, always use `Sentry.startSpan()`
4939
* or other span methods.
@@ -87,54 +77,6 @@ export class SentrySpan implements Span {
8777
};
8878
}
8979

90-
/**
91-
* Creates a new `Span` while setting the current `Span.id` as `parentSpanId`.
92-
* Also the `sampled` decision will be inherited.
93-
*
94-
* @deprecated Use `startSpan()`, `startSpanManual()` or `startInactiveSpan()` instead.
95-
*/
96-
public startChild(
97-
spanContext: Pick<
98-
SentrySpanArguments,
99-
Exclude<keyof SentrySpanArguments, 'sampled' | 'traceId' | 'parentSpanId'>
100-
> = {},
101-
): Span {
102-
const childSpan = new SentrySpan({
103-
...spanContext,
104-
parentSpanId: this._spanId,
105-
sampled: this._sampled,
106-
traceId: this._traceId,
107-
});
108-
109-
// To allow for interoperability we track the children of a span twice: Once with the span recorder (old) once with
110-
// the `addChildSpanToSpan`. Eventually we will only use `addChildSpanToSpan` and drop the span recorder.
111-
// To ensure interoperability with the `startSpan` API, `addChildSpanToSpan` is also called here.
112-
addChildSpanToSpan(this, childSpan);
113-
114-
const rootSpan = getRootSpan(this);
115-
116-
if (DEBUG_BUILD && rootSpan) {
117-
const opStr = (spanContext && spanContext.op) || '< unknown op >';
118-
const nameStr = spanToJSON(childSpan).description || '< unknown name >';
119-
const idStr = rootSpan.spanContext().spanId;
120-
121-
const logMessage = `[Tracing] Starting '${opStr}' span on transaction '${nameStr}' (${idStr}).`;
122-
logger.log(logMessage);
123-
this._logMessage = logMessage;
124-
}
125-
126-
const client = getClient();
127-
if (client) {
128-
client.emit('spanStart', childSpan);
129-
// If it has an endTimestamp, it's already ended
130-
if (spanContext.endTimestamp) {
131-
client.emit('spanEnd', childSpan);
132-
}
133-
}
134-
135-
return childSpan;
136-
}
137-
13880
/** @inheritdoc */
13981
public setAttribute(key: string, value: SpanAttributeValue | undefined): void {
14082
if (value === undefined) {
@@ -184,20 +126,9 @@ export class SentrySpan implements Span {
184126
if (this._endTime) {
185127
return;
186128
}
187-
const rootSpan = getRootSpan(this);
188-
if (
189-
DEBUG_BUILD &&
190-
// Don't call this for transactions
191-
rootSpan &&
192-
rootSpan.spanContext().spanId !== this._spanId
193-
) {
194-
const logMessage = this._logMessage;
195-
if (logMessage) {
196-
logger.log((logMessage as string).replace('Starting', 'Finishing'));
197-
}
198-
}
199129

200130
this._endTime = spanTimeInputToSeconds(endTimestamp);
131+
logSpanEnd(this);
201132

202133
this._onSpanEnded();
203134
}

packages/core/src/tracing/trace.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import type { ClientOptions, Scope, Span, SpanTimeInput, StartSpanOptions, TransactionArguments } from '@sentry/types';
1+
import type {
2+
ClientOptions,
3+
Scope,
4+
SentrySpanArguments,
5+
Span,
6+
SpanTimeInput,
7+
StartSpanOptions,
8+
TransactionArguments,
9+
} from '@sentry/types';
210

311
import { propagationContextFromHeaders } from '@sentry/utils';
412
import type { AsyncContextStrategy } from '../asyncContext';
@@ -18,9 +26,10 @@ import {
1826
spanToJSON,
1927
} from '../utils/spanUtils';
2028
import { getDynamicSamplingContextFromSpan } from './dynamicSamplingContext';
29+
import { logSpanStart } from './logSpans';
2130
import { sampleSpan } from './sampling';
2231
import { SentryNonRecordingSpan } from './sentryNonRecordingSpan';
23-
import type { SentrySpan } from './sentrySpan';
32+
import { SentrySpan } from './sentrySpan';
2433
import { SPAN_STATUS_ERROR } from './spanstatus';
2534
import { Transaction } from './transaction';
2635
import { setCapturedScopesOnSpan } from './utils';
@@ -223,8 +232,7 @@ function createChildSpanOrTransaction({
223232

224233
let span: Span;
225234
if (parentSpan && !forceTransaction) {
226-
// eslint-disable-next-line deprecation/deprecation
227-
span = parentSpan.startChild(spanContext);
235+
span = _startChild(parentSpan, spanContext);
228236
addChildSpanToSpan(parentSpan, span);
229237
} else if (parentSpan) {
230238
// If we forced a transaction but have a parent span, make sure to continue from the parent span, not the scope
@@ -262,6 +270,8 @@ function createChildSpanOrTransaction({
262270
});
263271
}
264272

273+
logSpanStart(span);
274+
265275
// TODO v8: Technically `startTransaction` can return undefined, which is not reflected by the types
266276
// This happens if tracing extensions have not been added
267277
// In this case, we just want to return a non-recording span
@@ -321,3 +331,32 @@ function _startTransaction(transactionContext: TransactionArguments): Transactio
321331

322332
return transaction;
323333
}
334+
335+
/**
336+
* Creates a new `Span` while setting the current `Span.id` as `parentSpanId`.
337+
* This inherits the sampling decision from the parent span.
338+
*/
339+
function _startChild(parentSpan: Span, spanContext: SentrySpanArguments): SentrySpan {
340+
const { spanId, traceId } = parentSpan.spanContext();
341+
const sampled = spanIsSampled(parentSpan);
342+
343+
const childSpan = new SentrySpan({
344+
...spanContext,
345+
parentSpanId: spanId,
346+
traceId,
347+
sampled,
348+
});
349+
350+
addChildSpanToSpan(parentSpan, childSpan);
351+
352+
const client = getClient();
353+
if (client) {
354+
client.emit('spanStart', childSpan);
355+
// If it has an endTimestamp, it's already ended
356+
if (spanContext.endTimestamp) {
357+
client.emit('spanEnd', childSpan);
358+
}
359+
}
360+
361+
return childSpan;
362+
}

packages/core/src/tracing/transaction.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,6 @@ export class Transaction extends SentrySpan implements TransactionInterface {
241241
transaction.measurements = this._measurements;
242242
}
243243

244-
DEBUG_BUILD && logger.log(`[Tracing] Finishing ${spanToJSON(this).op} transaction: ${this._name}.`);
245244
return transaction;
246245
}
247246
}

packages/core/test/lib/tracing/sentrySpan.test.ts

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
import { timestampInSeconds } from '@sentry/utils';
22
import { SentrySpan } from '../../../src/tracing/sentrySpan';
33
import { SPAN_STATUS_ERROR } from '../../../src/tracing/spanstatus';
4-
import {
5-
TRACE_FLAG_NONE,
6-
TRACE_FLAG_SAMPLED,
7-
spanIsSampled,
8-
spanToJSON,
9-
spanToTraceContext,
10-
} from '../../../src/utils/spanUtils';
4+
import { TRACE_FLAG_NONE, TRACE_FLAG_SAMPLED, spanToJSON, spanToTraceContext } from '../../../src/utils/spanUtils';
115

126
describe('SentrySpan', () => {
137
describe('name', () => {
@@ -26,17 +20,6 @@ describe('SentrySpan', () => {
2620
});
2721
});
2822

29-
describe('new SentrySpan', () => {
30-
test('simple', () => {
31-
const span = new SentrySpan({ sampled: true });
32-
// eslint-disable-next-line deprecation/deprecation
33-
const span2 = span.startChild();
34-
expect(spanToJSON(span2).parent_span_id).toBe(span.spanContext().spanId);
35-
expect(span.spanContext().traceId).toBe(span.spanContext().traceId);
36-
expect(spanIsSampled(span2)).toBe(spanIsSampled(span));
37-
});
38-
});
39-
4023
describe('setters', () => {
4124
test('setName', () => {
4225
const span = new SentrySpan({});

packages/core/test/lib/utils/spanUtils.test.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
SPAN_STATUS_OK,
88
SPAN_STATUS_UNSET,
99
SentrySpan,
10-
Transaction,
1110
addTracingExtensions,
1211
setCurrentClient,
1312
spanToTraceHeader,
@@ -247,15 +246,4 @@ describe('getRootSpan', () => {
247246
});
248247
});
249248
});
250-
251-
it('returns the root span of a legacy transaction & its children', () => {
252-
// eslint-disable-next-line deprecation/deprecation
253-
const root = new Transaction({ name: 'test' });
254-
255-
expect(getRootSpan(root)).toBe(root);
256-
257-
// eslint-disable-next-line deprecation/deprecation
258-
const childSpan = root.startChild({ name: 'child' });
259-
expect(getRootSpan(childSpan)).toBe(root);
260-
});
261249
});

0 commit comments

Comments
 (0)