Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/core/src/tracing/sentryNonRecordingSpan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
SpanAttributeValue,
SpanContextData,
SpanTimeInput,
StreamedSpanJSON,
} from '../types/span';
import type { SpanStatus } from '../types/spanStatus';
import { addNonEnumerableProperty } from '../utils/object';
Expand Down Expand Up @@ -108,6 +109,18 @@ export class SentryNonRecordingSpan implements Span {
public recordException(_exception: unknown, _time?: SpanTimeInput | undefined): void {
// noop
}

public getSpanJSON(): StreamedSpanJSON {
return {
name: '',
is_segment: false,
span_id: this._spanId,
trace_id: this._traceId,
start_timestamp: 0,
status: 'ok',
attributes: {},
};
}
}

/**
Expand Down
9 changes: 1 addition & 8 deletions packages/core/src/tracing/sentrySpan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,14 +292,7 @@ export class SentrySpan implements Span {
};
}

/**
* Get {@link StreamedSpanJSON} representation of this span.
*
* @hidden
* @internal This method is purely for internal purposes and should not be used outside
* of SDK code. If you need to get a JSON representation of a span,
* use `spanToJSON(span)` instead.
*/
/** @inheritdoc */
public getSpanJSON(): StreamedSpanJSON {
return {
name: this._name ?? '',
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/types/span.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,4 +326,9 @@ export interface Span {
* NOT USED IN SENTRY, only added for compliance with OTEL Span interface
*/
recordException(exception: unknown, time?: SpanTimeInput): void;

/**
* Get a {@link StreamedSpanJSON} representation of this span.
*/
getSpanJSON(): StreamedSpanJSON;
Comment thread
cursor[bot] marked this conversation as resolved.
}
104 changes: 8 additions & 96 deletions packages/core/src/utils/spanUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import { getCurrentScope } from '../currentScopes';
import type { Scope } from '../scope';
import {
SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
SEMANTIC_ATTRIBUTE_SENTRY_STATUS_MESSAGE,
} from '../semanticAttributes';
Expand All @@ -22,7 +20,6 @@ import type {
Span,
SpanAttributes,
SpanJSON,
SpanOrigin,
SpanTimeInput,
StreamedSpanJSON,
} from '../types/span';
Expand Down Expand Up @@ -167,40 +164,20 @@ function ensureTimestampInSeconds(timestamp: number): number {
* Convert a span to a static JSON representation.
*/
// Note: Because of this, we currently have a circular type dependency (which we opted out of in package.json).
// This is not avoidable as we need `spanToJSON` in `spanUtils.ts`, which in turn is needed by `span.ts` for backwards compatibility.
// This is not avoidable as we need `spanToJSON` in `spanUtils.ts`, which in turn is needed by `sentrySpan.ts` for backwards compatibility.
// And `spanToJSON` needs the Span class from `span.ts` to check here.
export function spanToStaticSpanJSON(span: Span): SpanJSON {
if (spanIsSentrySpan(span)) {
return span.getStaticSpanJSON();
}

const { spanId: span_id, traceId: trace_id } = span.spanContext();

// Handle a span from @opentelemetry/sdk-base-trace's `Span` class
if (spanIsOpenTelemetrySdkTraceBaseSpan(span)) {
const { attributes, startTime, name, endTime, status, links } = span;

return {
span_id,
trace_id,
data: attributes,
description: name,
parent_span_id: getOtelParentSpanId(span),
start_timestamp: spanTimeInputToSeconds(startTime),
// This is [0,0] by default in OTEL, in which case we want to interpret this as no end time
timestamp: spanTimeInputToSeconds(endTime) || undefined,
status: getStatusMessage(status),
op: attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP],
origin: attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] as SpanOrigin | undefined,
links: convertSpanLinksForEnvelope(links),
};
}

// Finally, at least we have `spanContext()`....
// because `spanToJSON` accepts a `Span` interface rather than a `SentrySpan` instance,
// we need to handle the case where the span is not a Sentry span.
// This should not actually happen in reality, but we need to handle it for type safety.
const ctx = span.spanContext();
return {
span_id,
trace_id,
Comment thread
cursor[bot] marked this conversation as resolved.
span_id: ctx.spanId,
trace_id: ctx.traceId,
start_timestamp: 0,
status: 'ok',
data: {},
Expand All @@ -211,56 +188,7 @@ export function spanToStaticSpanJSON(span: Span): SpanJSON {
* Convert a span to a JSON representation.
*/
export function spanToJSON(span: Span): StreamedSpanJSON {
if (spanIsSentrySpan(span)) {
return span.getSpanJSON();
}

const { spanId: span_id, traceId: trace_id } = span.spanContext();

// Handle a span from @opentelemetry/sdk-base-trace's `Span` class
if (spanIsOpenTelemetrySdkTraceBaseSpan(span)) {
const { attributes, startTime, name, endTime, status, links } = span;

return {
name,
span_id,
trace_id,
parent_span_id: getOtelParentSpanId(span),
start_timestamp: spanTimeInputToSeconds(startTime),
// This is [0,0] by default in OTEL, in which case we want to interpret this as no end time
end_timestamp: spanTimeInputToSeconds(endTime) || undefined,
is_segment: span === INTERNAL_getSegmentSpan(span),
status: getSimpleStatus(status),
attributes: addStatusMessageAttribute(attributes, status),
links: getStreamedSpanLinks(links),
};
}

// Finally, as a fallback, at least we have `spanContext()`....
// This should not actually happen in reality, but we need to handle it for type safety.
return {
span_id,
trace_id,
start_timestamp: 0,
name: '',
status: 'ok',
is_segment: span === INTERNAL_getSegmentSpan(span),
attributes: {},
};
}

/**
* In preparation for the next major of OpenTelemetry, we want to support
* looking up the parent span id according to the new API
* In OTel v1, the parent span id is accessed as `parentSpanId`
* In OTel v2, the parent span id is accessed as `spanId` on the `parentSpanContext`
*/
function getOtelParentSpanId(span: OpenTelemetrySdkTraceBaseSpan): string | undefined {
return 'parentSpanId' in span
? span.parentSpanId
: 'parentSpanContext' in span
? (span.parentSpanContext as { spanId?: string } | undefined)?.spanId
: undefined;
return span.getSpanJSON();
}

/**
Expand All @@ -282,28 +210,12 @@ export function streamedSpanJsonToSerializedSpan(spanJson: StreamedSpanJSON): Se
};
}

function spanIsOpenTelemetrySdkTraceBaseSpan(span: Span): span is OpenTelemetrySdkTraceBaseSpan {
const castSpan = span as Partial<OpenTelemetrySdkTraceBaseSpan>;
return !!castSpan.attributes && !!castSpan.startTime && !!castSpan.name && !!castSpan.endTime && !!castSpan.status;
}

/** Exported only for tests. */
export interface OpenTelemetrySdkTraceBaseSpan extends Span {
attributes: SpanAttributes;
startTime: SpanTimeInput;
name: string;
status: SpanStatus;
endTime: SpanTimeInput;
parentSpanId?: string;
links?: SpanLink[];
}

/**
* Sadly, due to circular dependency checks we cannot actually import the Span class here and check for instanceof.
* :( So instead we approximate this by checking if it has the `getSpanJSON` method.
*/
export function spanIsSentrySpan(span: Span): span is SentrySpan {
return typeof (span as SentrySpan).getSpanJSON === 'function';
return typeof (span as SentrySpan).getStaticSpanJSON === 'function';
}

/**
Expand Down
5 changes: 4 additions & 1 deletion packages/core/test/lib/tracing/spans/captureSpan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,10 @@ describe('captureSpan', () => {
client.on('processSpan', processSpanFn);
client.on('processSegmentSpan', processSegmentSpanFn);

const span = startInactiveSpan({ name: 'my-span', attributes: { 'sentry.op': 'http.client' } });
const span = withScope(scope => {
scope.setClient(client);
return startInactiveSpan({ name: 'my-span', attributes: { 'sentry.op': 'http.client' } });
});

captureSpan(span, client);

Expand Down
Loading
Loading