Skip to content

Commit 29fbe2e

Browse files
committed
feat(core): Add spanToJSON() method to get span properties
This is supposed to be an internal API and not necessarily to be used by users.
1 parent 89ca41d commit 29fbe2e

File tree

11 files changed

+162
-40
lines changed

11 files changed

+162
-40
lines changed

packages/core/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,12 @@
5656
"volta": {
5757
"extends": "../../package.json"
5858
},
59+
"madge": {
60+
"detectiveOptions": {
61+
"ts": {
62+
"skipTypeImports": true
63+
}
64+
}
65+
},
5966
"sideEffects": false
6067
}

packages/core/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ export { createCheckInEnvelope } from './checkin';
7272
export { hasTracingEnabled } from './utils/hasTracingEnabled';
7373
export { isSentryRequestUrl } from './utils/isSentryRequestUrl';
7474
export { handleCallbackErrors } from './utils/handleCallbackErrors';
75-
export { spanToTraceHeader } from './utils/spanUtils';
75+
export {
76+
spanToTraceHeader,
77+
spanToJSON,
78+
} from './utils/spanUtils';
7679
export { DEFAULT_ENVIRONMENT } from './constants';
7780
export { ModuleMetadata } from './integrations/metadata';
7881
export { RequestData } from './integrations/requestdata';

packages/core/src/tracing/span.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
SpanAttributeValue,
77
SpanAttributes,
88
SpanContext,
9+
SpanJSON,
910
SpanOrigin,
1011
SpanTimeInput,
1112
TraceContext,
@@ -369,22 +370,9 @@ export class Span implements SpanInterface {
369370
}
370371

371372
/**
372-
* @inheritDoc
373+
* Get JSON representation of this span.
373374
*/
374-
public toJSON(): {
375-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
376-
data?: { [key: string]: any };
377-
description?: string;
378-
op?: string;
379-
parent_span_id?: string;
380-
span_id: string;
381-
start_timestamp: number;
382-
status?: string;
383-
tags?: { [key: string]: Primitive };
384-
timestamp?: number;
385-
trace_id: string;
386-
origin?: SpanOrigin;
387-
} {
375+
public getSpanJSON(): SpanJSON {
388376
return dropUndefinedKeys({
389377
data: this._getData(),
390378
description: this.description,
@@ -400,6 +388,14 @@ export class Span implements SpanInterface {
400388
});
401389
}
402390

391+
/**
392+
* Convert the object to JSON.
393+
* @deprecated Use `spanToJSON(span)` instead.
394+
*/
395+
public toJSON(): SpanJSON {
396+
return this.getSpanJSON();
397+
}
398+
403399
/**
404400
* Get the merged data for this span.
405401
* For now, this combines `data` and `attributes` together,

packages/core/src/tracing/transaction.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ export class Transaction extends SpanClass implements TransactionInterface {
285285
// We don't want to override trace context
286286
trace: spanToTraceContext(this),
287287
},
288+
// TODO: Pass spans serialized via `spanToJSON()` here instead in v8.
288289
spans: finishedSpans,
289290
start_timestamp: this.startTimestamp,
290291
tags: this.tags,

packages/core/src/utils/spanUtils.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import type { Span, SpanTimeInput, TraceContext } from '@sentry/types';
1+
import type { Span, SpanJSON, SpanTimeInput, TraceContext } from '@sentry/types';
22
import { dropUndefinedKeys, generateSentryTraceHeader, timestampInSeconds } from '@sentry/utils';
3+
import type { Span as SpanClass } from '../tracing/span';
34

45
/**
56
* Convert a span to a trace context, which can be sent as the `trace` context in an event.
67
*/
78
export function spanToTraceContext(span: Span): TraceContext {
8-
const { data, description, op, parent_span_id, span_id, status, tags, trace_id, origin } = span.toJSON();
9+
const { spanId: span_id, traceId: trace_id } = span;
10+
const { data, description, op, parent_span_id, status, tags, origin } = spanToJSON(span);
911

1012
return dropUndefinedKeys({
1113
data,
@@ -54,3 +56,30 @@ function ensureTimestampInSeconds(timestamp: number): number {
5456
const isMs = timestamp > 9999999999;
5557
return isMs ? timestamp / 1000 : timestamp;
5658
}
59+
60+
/**
61+
* Convert a span to a JSON representation.
62+
* Note that all fields returned here are optional and need to be guarded against.
63+
*/
64+
export function spanToJSON(span: Span): Partial<SpanJSON> {
65+
if (spanIsSpanClass(span)) {
66+
return span.getSpanJSON();
67+
}
68+
69+
// Fallback: We also check for `.toJSON()` here...
70+
// eslint-disable-next-line deprecation/deprecation
71+
if (typeof span.toJSON === 'function') {
72+
// eslint-disable-next-line deprecation/deprecation
73+
return span.toJSON();
74+
}
75+
76+
return {};
77+
}
78+
79+
/**
80+
* Sadly, due to circular dependency checks we cannot actually import the Span class here and check for instanceof.
81+
* :( So instead we approximate this by checking if it has the `getSpanJSON` method.
82+
*/
83+
function spanIsSpanClass(span: Span): span is SpanClass {
84+
return typeof (span as SpanClass).getSpanJSON === 'function';
85+
}

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

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { TRACEPARENT_REGEXP, timestampInSeconds } from '@sentry/utils';
22
import { Span, spanToTraceHeader } from '../../../src';
3-
import { spanTimeInputToSeconds } from '../../../src/utils/spanUtils';
3+
import { spanTimeInputToSeconds, spanToJSON } from '../../../src/utils/spanUtils';
44

55
describe('spanToTraceHeader', () => {
66
test('simple', () => {
@@ -46,3 +46,72 @@ describe('spanTimeInputToSeconds', () => {
4646
expect(spanTimeInputToSeconds(timestamp)).toEqual(seconds + 0.000009);
4747
});
4848
});
49+
50+
describe('spanToJSON', () => {
51+
it('works with a simple span', () => {
52+
const span = new Span();
53+
expect(spanToJSON(span)).toEqual({
54+
span_id: span.spanId,
55+
trace_id: span.traceId,
56+
origin: 'manual',
57+
start_timestamp: span.startTimestamp,
58+
});
59+
});
60+
61+
it('works with a full span', () => {
62+
const span = new Span({
63+
name: 'test name',
64+
op: 'test op',
65+
parentSpanId: '1234',
66+
spanId: '5678',
67+
status: 'ok',
68+
tags: {
69+
foo: 'bar',
70+
},
71+
traceId: 'abcd',
72+
origin: 'auto',
73+
startTimestamp: 123,
74+
});
75+
76+
expect(spanToJSON(span)).toEqual({
77+
description: 'test name',
78+
op: 'test op',
79+
parent_span_id: '1234',
80+
span_id: '5678',
81+
status: 'ok',
82+
tags: {
83+
foo: 'bar',
84+
},
85+
trace_id: 'abcd',
86+
origin: 'auto',
87+
start_timestamp: 123,
88+
});
89+
});
90+
91+
it('works with a custom class without spanToJSON', () => {
92+
const span = {
93+
toJSON: () => {
94+
return {
95+
span_id: 'span_id',
96+
trace_id: 'trace_id',
97+
origin: 'manual',
98+
start_timestamp: 123,
99+
};
100+
},
101+
} as unknown as Span;
102+
103+
expect(spanToJSON(span)).toEqual({
104+
span_id: 'span_id',
105+
trace_id: 'trace_id',
106+
origin: 'manual',
107+
start_timestamp: 123,
108+
});
109+
});
110+
111+
it('returns empty object if span does not have getter methods', () => {
112+
// eslint-disable-next-line
113+
const span = new Span().toJSON();
114+
115+
expect(spanToJSON(span as unknown as Span)).toEqual({});
116+
});
117+
});

packages/node-experimental/test/integration/transactions.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { SpanKind, TraceFlags, context, trace } from '@opentelemetry/api';
22
import type { SpanProcessor } from '@opentelemetry/sdk-trace-base';
33
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
4+
import { spanToJSON } from '@sentry/core';
45
import { SentrySpanProcessor, getCurrentHub, setPropagationContextOnContext } from '@sentry/opentelemetry';
56
import type { Integration, PropagationContext, TransactionEvent } from '@sentry/types';
67
import { logger } from '@sentry/utils';
@@ -145,7 +146,7 @@ describe('Integration | Transactions', () => {
145146

146147
// note: Currently, spans do not have any context/span added to them
147148
// This is the same behavior as for the "regular" SDKs
148-
expect(spans.map(span => span.toJSON())).toEqual([
149+
expect(spans.map(span => spanToJSON(span))).toEqual([
149150
{
150151
data: { 'otel.kind': 'INTERNAL' },
151152
description: 'inner span 1',
@@ -399,7 +400,7 @@ describe('Integration | Transactions', () => {
399400

400401
// note: Currently, spans do not have any context/span added to them
401402
// This is the same behavior as for the "regular" SDKs
402-
expect(spans.map(span => span.toJSON())).toEqual([
403+
expect(spans.map(span => spanToJSON(span))).toEqual([
403404
{
404405
data: { 'otel.kind': 'INTERNAL' },
405406
description: 'inner span 1',

packages/opentelemetry/test/custom/transaction.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { spanToJSON } from '@sentry/core';
12
import { getCurrentHub } from '../../src/custom/hub';
23
import { OpenTelemetryScope } from '../../src/custom/scope';
34
import { OpenTelemetryTransaction, startTransaction } from '../../src/custom/transaction';
@@ -157,7 +158,7 @@ describe('startTranscation', () => {
157158
spanMetadata: {},
158159
});
159160

160-
expect(transaction.toJSON()).toEqual(
161+
expect(spanToJSON(transaction)).toEqual(
161162
expect.objectContaining({
162163
origin: 'manual',
163164
span_id: expect.any(String),
@@ -186,7 +187,7 @@ describe('startTranscation', () => {
186187
spanMetadata: {},
187188
});
188189

189-
expect(transaction.toJSON()).toEqual(
190+
expect(spanToJSON(transaction)).toEqual(
190191
expect.objectContaining({
191192
origin: 'manual',
192193
span_id: 'span1',

packages/opentelemetry/test/integration/transactions.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { addBreadcrumb, setTag } from '@sentry/core';
44
import type { PropagationContext, TransactionEvent } from '@sentry/types';
55
import { logger } from '@sentry/utils';
66

7+
import { spanToJSON } from '@sentry/core';
78
import { getCurrentHub } from '../../src/custom/hub';
89
import { SentrySpanProcessor } from '../../src/spanProcessor';
910
import { startInactiveSpan, startSpan } from '../../src/trace';
@@ -142,7 +143,7 @@ describe('Integration | Transactions', () => {
142143

143144
// note: Currently, spans do not have any context/span added to them
144145
// This is the same behavior as for the "regular" SDKs
145-
expect(spans.map(span => span.toJSON())).toEqual([
146+
expect(spans.map(span => spanToJSON(span))).toEqual([
146147
{
147148
data: { 'otel.kind': 'INTERNAL' },
148149
description: 'inner span 1',
@@ -393,7 +394,7 @@ describe('Integration | Transactions', () => {
393394

394395
// note: Currently, spans do not have any context/span added to them
395396
// This is the same behavior as for the "regular" SDKs
396-
expect(spans.map(span => span.toJSON())).toEqual([
397+
expect(spans.map(span => spanToJSON(span))).toEqual([
397398
{
398399
data: { 'otel.kind': 'INTERNAL' },
399400
description: 'inner span 1',

packages/types/src/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,15 @@ export type {
8989

9090
// eslint-disable-next-line deprecation/deprecation
9191
export type { Severity, SeverityLevel } from './severity';
92-
export type { Span, SpanContext, SpanOrigin, SpanAttributeValue, SpanAttributes, SpanTimeInput } from './span';
92+
export type {
93+
Span,
94+
SpanContext,
95+
SpanOrigin,
96+
SpanAttributeValue,
97+
SpanAttributes,
98+
SpanTimeInput,
99+
SpanJSON,
100+
} from './span';
93101
export type { StackFrame } from './stackframe';
94102
export type { Stacktrace, StackParser, StackLineParser, StackLineParserFn } from './stacktrace';
95103
export type { TextEncoderInternal } from './textencoder';

packages/types/src/span.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,21 @@ export type SpanAttributes = Record<string, SpanAttributeValue | undefined>;
2828
/** This type is aligned with the OpenTelemetry TimeInput type. */
2929
export type SpanTimeInput = HrTime | number | Date;
3030

31+
/** A JSON representation of a span. */
32+
export interface SpanJSON {
33+
data?: { [key: string]: any };
34+
description?: string;
35+
op?: string;
36+
parent_span_id?: string;
37+
span_id: string;
38+
start_timestamp: number;
39+
status?: string;
40+
tags?: { [key: string]: Primitive };
41+
timestamp?: number;
42+
trace_id: string;
43+
origin?: SpanOrigin;
44+
}
45+
3146
/** Interface holding all properties that can be set on a Span on creation. */
3247
export interface SpanContext {
3348
/**
@@ -254,18 +269,9 @@ export interface Span extends SpanContext {
254269
*/
255270
getTraceContext(): TraceContext;
256271

257-
/** Convert the object to JSON */
258-
toJSON(): {
259-
data?: { [key: string]: any };
260-
description?: string;
261-
op?: string;
262-
parent_span_id?: string;
263-
span_id: string;
264-
start_timestamp: number;
265-
status?: string;
266-
tags?: { [key: string]: Primitive };
267-
timestamp?: number;
268-
trace_id: string;
269-
origin?: SpanOrigin;
270-
};
272+
/**
273+
* Convert the object to JSON.
274+
* @deprecated Use `spanToJSON(span)` instead.
275+
*/
276+
toJSON(): SpanJSON;
271277
}

0 commit comments

Comments
 (0)