Skip to content

Commit 2b2a467

Browse files
committed
ref: Emit spanStart and spanEnd hooks
1 parent cd9b896 commit 2b2a467

File tree

7 files changed

+110
-1
lines changed

7 files changed

+110
-1
lines changed

packages/core/src/baseclient.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type {
2222
Session,
2323
SessionAggregates,
2424
SeverityLevel,
25+
Span,
2526
StartSpanOptions,
2627
Transaction,
2728
TransactionEvent,
@@ -422,6 +423,12 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
422423
/** @inheritdoc */
423424
public on(hook: 'finishTransaction', callback: (transaction: Transaction) => void): void;
424425

426+
/** @inheritdoc */
427+
public on(hook: 'spanStart', callback: (span: Span) => void): void;
428+
429+
/** @inheritdoc */
430+
public on(hook: 'spanEnd', callback: (span: Span) => void): void;
431+
425432
/** @inheritdoc */
426433
public on(hook: 'beforeEnvelope', callback: (envelope: Envelope) => void): void;
427434

@@ -472,6 +479,12 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
472479
/** @inheritdoc */
473480
public emit(hook: 'finishTransaction', transaction: Transaction): void;
474481

482+
/** @inheritdoc */
483+
public emit(hook: 'spanStart', span: Span): void;
484+
485+
/** @inheritdoc */
486+
public emit(hook: 'spanEnd', span: Span): void;
487+
475488
/** @inheritdoc */
476489
public emit(hook: 'beforeEnvelope', envelope: Envelope): void;
477490

packages/core/src/tracing/hubextensions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ function _startTransaction(
4848
}
4949
if (client) {
5050
client.emit('startTransaction', transaction);
51+
client.emit('spanStart', transaction);
5152
}
5253
return transaction;
5354
}
@@ -95,6 +96,7 @@ export function startIdleTransaction(
9596
}
9697
if (client) {
9798
client.emit('startTransaction', transaction);
99+
client.emit('spanStart', transaction);
98100
}
99101
return transaction;
100102
}

packages/core/src/tracing/sentrySpan.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
Transaction,
1414
} from '@sentry/types';
1515
import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils';
16+
import { getClient } from '../currentScopes';
1617

1718
import { DEBUG_BUILD } from '../debug-build';
1819
import { getMetricSummaryJsonForSpan } from '../metrics/metric-summary';
@@ -277,7 +278,7 @@ export class SentrySpan implements Span {
277278
* @deprecated Use `startSpan()`, `startSpanManual()` or `startInactiveSpan()` instead.
278279
*/
279280
public startChild(
280-
spanContext?: Pick<SpanContext, Exclude<keyof SpanContext, 'sampled' | 'traceId' | 'parentSpanId'>>,
281+
spanContext: Pick<SpanContext, Exclude<keyof SpanContext, 'sampled' | 'traceId' | 'parentSpanId'>> = {},
281282
): Span {
282283
const childSpan = new SentrySpan({
283284
...spanContext,
@@ -315,6 +316,15 @@ export class SentrySpan implements Span {
315316
this._logMessage = logMessage;
316317
}
317318

319+
const client = getClient();
320+
if (client) {
321+
client.emit('spanStart', childSpan);
322+
// If it has an endTimestamp, it's already ended
323+
if (spanContext.endTimestamp) {
324+
client.emit('spanEnd', childSpan);
325+
}
326+
}
327+
318328
return childSpan;
319329
}
320330

@@ -397,6 +407,8 @@ export class SentrySpan implements Span {
397407
}
398408

399409
this._endTime = spanTimeInputToSeconds(endTimestamp);
410+
411+
this._onSpanEnded();
400412
}
401413

402414
/**
@@ -499,4 +511,12 @@ export class SentrySpan implements Span {
499511

500512
return hasData ? data : attributes;
501513
}
514+
515+
/** Emit `spanEnd` when the span is ended. */
516+
private _onSpanEnded(): void {
517+
const client = getClient();
518+
if (client) {
519+
client.emit('spanEnd', this);
520+
}
521+
}
502522
}

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,3 +1168,54 @@ describe('continueTrace', () => {
11681168
expect(ctx).toEqual(expectedContext);
11691169
});
11701170
});
1171+
1172+
describe('span hooks', () => {
1173+
beforeEach(() => {
1174+
addTracingExtensions();
1175+
1176+
getCurrentScope().clear();
1177+
getIsolationScope().clear();
1178+
getGlobalScope().clear();
1179+
1180+
const options = getDefaultTestClientOptions({ tracesSampleRate: 0.0 });
1181+
client = new TestClient(options);
1182+
setCurrentClient(client);
1183+
client.init();
1184+
});
1185+
1186+
afterEach(() => {
1187+
jest.clearAllMocks();
1188+
});
1189+
1190+
it('correctly emits span hooks', () => {
1191+
const startedSpans: string[] = [];
1192+
const endedSpans: string[] = [];
1193+
1194+
client.on('spanStart', span => {
1195+
startedSpans.push(spanToJSON(span).description || '');
1196+
});
1197+
1198+
client.on('spanEnd', span => {
1199+
endedSpans.push(spanToJSON(span).description || '');
1200+
});
1201+
1202+
startSpan({ name: 'span1' }, () => {
1203+
startSpan({ name: 'span2' }, () => {
1204+
const span = startInactiveSpan({ name: 'span3' });
1205+
1206+
startSpanManual({ name: 'span5' }, span => {
1207+
startInactiveSpan({ name: 'span4' });
1208+
span?.end();
1209+
});
1210+
1211+
span?.end();
1212+
});
1213+
});
1214+
1215+
expect(startedSpans).toHaveLength(5);
1216+
expect(endedSpans).toHaveLength(4);
1217+
1218+
expect(startedSpans).toEqual(['span1', 'span2', 'span3', 'span5', 'span4']);
1219+
expect(endedSpans).toEqual(['span5', 'span3', 'span2', 'span1']);
1220+
});
1221+
});

packages/opentelemetry/src/custom/transaction.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export function startTransaction(hub: HubInterface, transactionContext: Transact
1818

1919
if (client) {
2020
client.emit('startTransaction', transaction);
21+
client.emit('spanStart', transaction);
2122
}
2223
return transaction;
2324
}

packages/opentelemetry/src/spanProcessor.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ export class SentrySpanProcessor extends BatchSpanProcessor implements SpanProce
6767
public onStart(span: Span, parentContext: Context): void {
6868
onSpanStart(span, parentContext);
6969

70+
// TODO (v8): Trigger client `spanStart` & `spanEnd` in here,
71+
// once we decoupled opentelemetry from SentrySpan
72+
7073
DEBUG_BUILD && logger.log(`[Tracing] Starting span "${span.name}" (${span.spanContext().spanId})`);
7174

7275
return super.onStart(span, parentContext);

packages/types/src/client.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { Scope } from './scope';
1414
import type { SdkMetadata } from './sdkmetadata';
1515
import type { Session, SessionAggregates } from './session';
1616
import type { SeverityLevel } from './severity';
17+
import type { Span } from './span';
1718
import type { StartSpanOptions } from './startSpanOptions';
1819
import type { Transaction } from './transaction';
1920
import type { Transport, TransportMakeRequestResponse } from './transport';
@@ -193,6 +194,18 @@ export interface Client<O extends ClientOptions = ClientOptions> {
193194
*/
194195
on(hook: 'finishTransaction', callback: (transaction: Transaction) => void): void;
195196

197+
/**
198+
* Register a callback for whenever a span is started.
199+
* Receives the span as argument.
200+
*/
201+
on(hook: 'spanStart', callback: (span: Span) => void): void;
202+
203+
/**
204+
* Register a callback for whenever a span is ended.
205+
* Receives the span as argument.
206+
*/
207+
on(hook: 'spanEnd', callback: (span: Span) => void): void;
208+
196209
/**
197210
* Register a callback for transaction start and finish.
198211
*/
@@ -269,6 +282,12 @@ export interface Client<O extends ClientOptions = ClientOptions> {
269282
*/
270283
emit(hook: 'finishTransaction', transaction: Transaction): void;
271284

285+
/** Fire a hook whener a span starts. */
286+
emit(hook: 'spanStart', span: Span): void;
287+
288+
/** Fire a hook whener a span ends. */
289+
emit(hook: 'spanEnd', span: Span): void;
290+
272291
/*
273292
* Fire a hook event for envelope creation and sending. Expects to be given an envelope as the
274293
* second argument.

0 commit comments

Comments
 (0)