Skip to content

Commit 227cc9f

Browse files
committed
rewrite to captureSpan
1 parent 1973b14 commit 227cc9f

File tree

7 files changed

+41
-122
lines changed

7 files changed

+41
-122
lines changed
Lines changed: 18 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,12 @@
1-
import type { Client, IntegrationFn, Scope, ScopeData, Span, SpanAttributes, SpanV2JSON } from '@sentry/core';
1+
import type { Client, IntegrationFn, Span, SpanV2JSON } from '@sentry/core';
22
import {
3+
captureSpan,
34
createSpanV2Envelope,
45
debug,
56
defineIntegration,
6-
getCapturedScopesOnSpan,
77
getDynamicSamplingContextFromSpan,
8-
getGlobalScope,
98
INTERNAL_getSegmentSpan,
109
isV2BeforeSendSpanCallback,
11-
mergeScopeData,
12-
reparentChildSpans,
13-
SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME,
14-
SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT,
15-
SEMANTIC_ATTRIBUTE_SENTRY_RELEASE,
16-
SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME,
17-
SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION,
18-
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID,
19-
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME,
20-
SEMANTIC_ATTRIBUTE_USER_EMAIL,
21-
SEMANTIC_ATTRIBUTE_USER_ID,
22-
SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS,
23-
SEMANTIC_ATTRIBUTE_USER_USERNAME,
24-
shouldIgnoreSpan,
2510
showSpanDropWarning,
2611
spanToV2JSON,
2712
} from '@sentry/core';
@@ -72,7 +57,7 @@ export const spanStreamingIntegration = defineIntegration(((userOptions?: Partia
7257
return;
7358
}
7459

75-
client.on('spanEnd', span => {
60+
client.on('enqueueSpan', span => {
7661
const spanTreeMapKey = getSpanTreeMapKey(span);
7762
const spanBuffer = spanTreeMap.get(spanTreeMapKey);
7863
if (spanBuffer) {
@@ -82,10 +67,14 @@ export const spanStreamingIntegration = defineIntegration(((userOptions?: Partia
8267
}
8368
});
8469

70+
client.on('afterSpanEnd', span => {
71+
captureSpan(span, client);
72+
});
73+
8574
// For now, we send all spans on local segment (root) span end.
8675
// TODO: This will change once we have more concrete ideas about a universal SDK data buffer.
87-
client.on('segmentSpanEnd', segmentSpan => {
88-
processAndSendSpans(segmentSpan, {
76+
client.on('afterSegmentSpanEnd', segmentSpan => {
77+
sendSegment(segmentSpan, {
8978
spanTreeMap: spanTreeMap,
9079
client,
9180
batchLimit: options.batchLimit,
@@ -110,7 +99,7 @@ function getSpanTreeMapKey(span: Span): string {
11099
return `${span.spanContext().traceId}-${INTERNAL_getSegmentSpan(span).spanContext().spanId}`;
111100
}
112101

113-
function processAndSendSpans(
102+
function sendSegment(
114103
segmentSpan: Span,
115104
{ client, spanTreeMap, batchLimit, beforeSendSpan }: SpanProcessingOptions,
116105
): void {
@@ -123,54 +112,17 @@ function processAndSendSpans(
123112
return;
124113
}
125114

126-
const segmentSpanJson = spanToV2JSON(segmentSpan);
127-
128-
for (const span of spansOfTrace) {
129-
applyCommonSpanAttributes(span, segmentSpanJson, client);
130-
}
131-
132-
const { ignoreSpans } = client.getOptions();
133-
134-
// 1. Check if the entire span tree is ignored by ignoreSpans
135-
if (ignoreSpans?.length && shouldIgnoreSpan(segmentSpanJson, ignoreSpans)) {
136-
client.recordDroppedEvent('before_send', 'span', spansOfTrace.size);
137-
spanTreeMap.delete(spanTreeMapKey);
138-
return;
139-
}
140-
141-
const serializedSpans = Array.from(spansOfTrace ?? []).map(s => {
142-
const serialized = spanToV2JSON(s);
143-
// remove internal span attributes we don't need to send.
144-
delete serialized.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME];
145-
return serialized;
146-
});
147-
148-
const processedSpans = [];
149-
let ignoredSpanCount = 0;
150-
151-
for (const span of serializedSpans) {
152-
// 2. Check if child spans should be ignored
153-
const isChildSpan = span.span_id !== segmentSpan.spanContext().spanId;
154-
if (ignoreSpans?.length && isChildSpan && shouldIgnoreSpan(span, ignoreSpans)) {
155-
reparentChildSpans(serializedSpans, span);
156-
ignoredSpanCount++;
157-
// drop this span by not adding it to the processedSpans array
158-
continue;
115+
const finalSpans = Array.from(spansOfTrace).map(span => {
116+
const spanJson = spanToV2JSON(span);
117+
if (beforeSendSpan) {
118+
return applyBeforeSendSpanCallback(spanJson, beforeSendSpan);
159119
}
160-
161-
// 3. Apply beforeSendSpan callback
162-
// TODO: validate beforeSendSpan result/pass in a copy and merge afterwards
163-
const processedSpan = beforeSendSpan ? applyBeforeSendSpanCallback(span, beforeSendSpan) : span;
164-
processedSpans.push(processedSpan);
165-
}
166-
167-
if (ignoredSpanCount) {
168-
client.recordDroppedEvent('before_send', 'span', ignoredSpanCount);
169-
}
120+
return spanJson;
121+
});
170122

171123
const batches: SpanV2JSON[][] = [];
172-
for (let i = 0; i < processedSpans.length; i += batchLimit) {
173-
batches.push(processedSpans.slice(i, i + batchLimit));
124+
for (let i = 0; i < finalSpans.length; i += batchLimit) {
125+
batches.push(finalSpans.slice(i, i + batchLimit));
174126
}
175127

176128
DEBUG_BUILD && debug.log(`Sending trace ${traceId} in ${batches.length} batch${batches.length === 1 ? '' : 'es'}`);
@@ -190,35 +142,6 @@ function processAndSendSpans(
190142
spanTreeMap.delete(spanTreeMapKey);
191143
}
192144

193-
function applyCommonSpanAttributes(span: Span, serializedSegmentSpan: SpanV2JSON, client: Client): void {
194-
const sdk = client.getSdkMetadata();
195-
const { release, environment, sendDefaultPii } = client.getOptions();
196-
197-
const { isolationScope: spanIsolationScope, scope: spanScope } = getCapturedScopesOnSpan(span);
198-
199-
const originalAttributeKeys = Object.keys(spanToV2JSON(span).attributes ?? {});
200-
201-
const finalScopeData = getFinalScopeData(spanIsolationScope, spanScope);
202-
203-
// avoid overwriting any previously set attributes (from users or potentially our SDK instrumentation)
204-
setAttributesIfNotPresent(span, originalAttributeKeys, {
205-
[SEMANTIC_ATTRIBUTE_SENTRY_RELEASE]: release,
206-
[SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT]: environment,
207-
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME]: serializedSegmentSpan.name,
208-
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: serializedSegmentSpan.span_id,
209-
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: sdk?.sdk?.name,
210-
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: sdk?.sdk?.version,
211-
...(sendDefaultPii
212-
? {
213-
[SEMANTIC_ATTRIBUTE_USER_ID]: finalScopeData.user?.id,
214-
[SEMANTIC_ATTRIBUTE_USER_EMAIL]: finalScopeData.user?.email,
215-
[SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS]: finalScopeData.user?.ip_address ?? undefined,
216-
[SEMANTIC_ATTRIBUTE_USER_USERNAME]: finalScopeData.user?.username,
217-
}
218-
: {}),
219-
});
220-
}
221-
222145
function applyBeforeSendSpanCallback(span: SpanV2JSON, beforeSendSpan: (span: SpanV2JSON) => SpanV2JSON): SpanV2JSON {
223146
const modifedSpan = beforeSendSpan(span);
224147
if (!modifedSpan) {
@@ -227,23 +150,3 @@ function applyBeforeSendSpanCallback(span: SpanV2JSON, beforeSendSpan: (span: Sp
227150
}
228151
return modifedSpan;
229152
}
230-
231-
function setAttributesIfNotPresent(span: Span, originalAttributeKeys: string[], newAttributes: SpanAttributes): void {
232-
Object.keys(newAttributes).forEach(key => {
233-
if (!originalAttributeKeys.includes(key)) {
234-
span.setAttribute(key, newAttributes[key]);
235-
}
236-
});
237-
}
238-
239-
// TODO: Extract this to a helper in core. It's used in multiple places.
240-
function getFinalScopeData(isolationScope: Scope | undefined, scope: Scope | undefined): ScopeData {
241-
const finalScopeData = getGlobalScope().getScopeData();
242-
if (isolationScope) {
243-
mergeScopeData(finalScopeData, isolationScope.getScopeData());
244-
}
245-
if (scope) {
246-
mergeScopeData(finalScopeData, scope.getScopeData());
247-
}
248-
return finalScopeData;
249-
}

packages/core/src/client.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -608,13 +608,19 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
608608
*/
609609
public on(hook: 'spanEnd', callback: (span: Span) => void): () => void;
610610

611+
// Hooks reserved for Span-First span processing:
611612
/**
612613
* Register a callback for after a span is ended.
613-
* NOTE: The span cannot be mutated anymore in this callback.
614-
* Receives the span as argument.
615-
* @returns {() => void} A function that, when executed, removes the registered callback.
616614
*/
617-
public on(hook: 'segmentSpanEnd', callback: (span: Span) => void): () => void;
615+
public on(hook: 'afterSpanEnd', callback: (span: Span) => void): () => void;
616+
/**
617+
* Register a callback for after a segment span is ended.
618+
*/
619+
public on(hook: 'afterSegmentSpanEnd', callback: (span: Span) => void): () => void;
620+
/**
621+
* Register a callback for when the span is ready to be enqueued into the span buffer.
622+
*/
623+
public on(hook: 'enqueueSpan', callback: (span: Span) => void): () => void;
618624

619625
/**
620626
* Register a callback for when an idle span is allowed to auto-finish.
@@ -888,8 +894,13 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
888894
/** Fire a hook whenever a span ends. */
889895
public emit(hook: 'spanEnd', span: Span): void;
890896

891-
/** Fire a hook whenever a segment span ends. */
892-
public emit(hook: 'segmentSpanEnd', span: Span): void;
897+
// Hooks reserved for Span-First span processing:
898+
/** Fire a hook after the `spanEnd` hook */
899+
public emit(hook: 'afterSpanEnd', span: Span): void;
900+
/** Fire a hook after the `segmentSpanEnd` hook is fired. */
901+
public emit(hook: 'afterSegmentSpanEnd', span: Span): void;
902+
/** Fire a hook after a span ready to be enqueued into the span buffer. */
903+
public emit(hook: 'enqueueSpan', span: Span): void;
893904

894905
/**
895906
* Fire a hook indicating that an idle span is allowed to auto finish.

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export {
8989
spanToV2JSON,
9090
showSpanDropWarning,
9191
} from './utils/spanUtils';
92+
export { captureSpan } from './spans/captureSpan';
9293
export { attributesFromObject } from './utils/attributes';
9394
export { _setSpanForScope as _INTERNAL_setSpanForScope } from './utils/spanOnScope';
9495
export { parseSampleRate } from './utils/parseSampleRate';

packages/core/src/tracing/sentrySpan.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ export class SentrySpan implements Span {
316316
const client = getClient();
317317
if (client) {
318318
client.emit('spanEnd', this);
319+
client.emit('afterSpanEnd', this);
319320
}
320321

321322
// A segment span is basically the root span of a local span tree.
@@ -341,7 +342,7 @@ export class SentrySpan implements Span {
341342
return;
342343
} else if (client?.getOptions().traceLifecycle === 'stream') {
343344
// TODO (spans): Remove standalone span custom logic in favor of sending simple v2 web vital spans
344-
client?.emit('segmentSpanEnd', this);
345+
client?.emit('afterSegmentSpanEnd', this);
345346
return;
346347
}
347348

packages/core/src/tracing/trace.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ function _startChildSpan(parentSpan: Span, scope: Scope, spanArguments: SentrySp
490490
// If it has an endTimestamp, it's already ended
491491
if (spanArguments.endTimestamp) {
492492
client.emit('spanEnd', childSpan);
493+
client.emit('afterSpanEnd', childSpan);
493494
}
494495
}
495496

packages/core/src/types-hoist/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { RawAttributes } from '../attributes';
12
import type { CaptureContext } from '../scope';
23
import type { Breadcrumb, BreadcrumbHint } from './breadcrumb';
34
import type { ErrorEvent, EventHint, TransactionEvent } from './event';

packages/opentelemetry/src/spanProcessor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ function onSpanEnd(span: Span): void {
5656

5757
const client = getClient();
5858
client?.emit('spanEnd', span);
59+
client?.emit('afterSpanEnd', span);
5960
}
6061

6162
/**

0 commit comments

Comments
 (0)