Skip to content

Commit

Permalink
feat(core): Create types and utilities for span links
Browse files Browse the repository at this point in the history
  • Loading branch information
s1gr1d committed Feb 12, 2025
1 parent cea9484 commit 10337a5
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 33 deletions.
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export { handleCallbackErrors } from './utils/handleCallbackErrors';
export { parameterize } from './utils/parameterize';
export { addAutoIpAddressToSession, addAutoIpAddressToUser } from './utils/ipAddress';
export {
convertSpanLinksForEnvelope,
spanToTraceHeader,
spanToJSON,
spanIsSampled,
Expand Down
16 changes: 2 additions & 14 deletions packages/core/src/tracing/sentryNonRecordingSpan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,24 +69,12 @@ export class SentryNonRecordingSpan implements Span {
return this;
}

/**
* This should generally not be used,
* but we need it for being compliant with the OTEL Span interface.
*
* @hidden
* @internal
*/
/** @inheritDoc */
public addLink(_link: unknown): this {
return this;
}

/**
* This should generally not be used,
* but we need it for being compliant with the OTEL Span interface.
*
* @hidden
* @internal
*/
/** @inheritDoc */
public addLinks(_links: unknown[]): this {
return this;
}
Expand Down
16 changes: 2 additions & 14 deletions packages/core/src/tracing/sentrySpan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,24 +109,12 @@ export class SentrySpan implements Span {
}
}

/**
* This should generally not be used,
* but it is needed for being compliant with the OTEL Span interface.
*
* @hidden
* @internal
*/
/** @inheritDoc */
public addLink(_link: unknown): this {
return this;
}

/**
* This should generally not be used,
* but it is needed for being compliant with the OTEL Span interface.
*
* @hidden
* @internal
*/
/** @inheritDoc */
public addLinks(_links: unknown[]): this {
return this;
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/types-hoist/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export type {
SpanContextData,
TraceFlag,
} from './span';
export type { SpanLink, SpanLinkJSON } from './link';
export type { SpanStatus } from './spanStatus';
export type { TimedEvent } from './timedEvent';
export type { StackFrame } from './stackframe';
Expand Down
30 changes: 30 additions & 0 deletions packages/core/src/types-hoist/link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { SpanAttributeValue, SpanContextData } from './span';

type SpanLinkAttributes = {
/**
* Setting the link type to 'previous_trace' helps the Sentry product linking to the previous trace
*/
'sentry.link.type'?: string | 'previous_trace';
} & Record<string, SpanAttributeValue | undefined>;

export interface SpanLink {
/**
* Contains the SpanContext of the span to link to
*/
context: SpanContextData;
/**
* A key-value pair with primitive values or an array of primitive values
*/
attributes?: SpanLinkAttributes;
}

/**
* Link interface for the event envelope item. It's a flattened representation of `SpanLink`.
* Can include additional fields defined by OTel.
*/
export interface SpanLinkJSON extends Record<string, unknown> {
span_id: string;
trace_id: string;
sampled?: boolean;
attributes?: SpanLinkAttributes;
}
15 changes: 11 additions & 4 deletions packages/core/src/types-hoist/span.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SpanLink, SpanLinkJSON } from './link';
import type { Measurements } from './measurement';
import type { HrTime } from './opentelemetry';
import type { SpanStatus } from './spanStatus';
Expand Down Expand Up @@ -50,6 +51,7 @@ export interface SpanJSON {
measurements?: Measurements;
is_segment?: boolean;
segment_id?: string;
links?: SpanLinkJSON[];
}

// These are aligned with OpenTelemetry trace flags
Expand Down Expand Up @@ -249,14 +251,19 @@ export interface Span {
addEvent(name: string, attributesOrStartTime?: SpanAttributes | SpanTimeInput, startTime?: SpanTimeInput): this;

/**
* NOT USED IN SENTRY, only added for compliance with OTEL Span interface
* Associates this span with a related span. Links can reference spans from the same or different trace
* and are typically used for batch operations, cross-trace scenarios, or scatter/gather patterns.
*
* Prefer setting links during span creation when possible to support sampling decisions.
* @param link - The link containing the context of the span to link to and optional attributes
*/
addLink(link: unknown): this;
addLink(link: SpanLink): this;

/**
* NOT USED IN SENTRY, only added for compliance with OTEL Span interface
* Associates this span with multiple related spans. See {@link addLink} for more details.
* @param links - Array of links to associate with this span
*/
addLinks(links: unknown): this;
addLinks(links: SpanLink[]): this;

/**
* NOT USED IN SENTRY, only added for compliance with OTEL Span interface
Expand Down
17 changes: 16 additions & 1 deletion packages/core/src/utils/spanUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import type {
Span,
SpanAttributes,
SpanJSON,
SpanLink,
SpanLinkJSON,
SpanOrigin,
SpanStatus,
SpanTimeInput,
Expand Down Expand Up @@ -81,6 +83,19 @@ export function spanToTraceHeader(span: Span): string {
return generateSentryTraceHeader(traceId, spanId, sampled);
}

/**
* Converts the span links array to a flattened version to be sent within an envelope
*/
export function convertSpanLinksForEnvelope(links: SpanLink[]): SpanLinkJSON[] {
return links.map(({ context: { spanId, traceId, traceFlags, ...restContext }, attributes }) => ({
span_id: spanId,
trace_id: traceId,
sampled: traceFlags === TRACE_FLAG_SAMPLED,
attributes,
...restContext,
}));
}

/**
* Convert a span time input into a timestamp in seconds.
*/
Expand Down Expand Up @@ -180,7 +195,7 @@ function spanIsSentrySpan(span: Span): span is SentrySpan {
* However, this has a slightly different semantic, as it also returns false if the span is finished.
* So in the case where this distinction is important, use this method.
*/
export function spanIsSampled(span: Span): boolean {
export function spanIsSampled(span: { spanContext: Span['spanContext'] }): boolean {
// We align our trace flags with the ones OpenTelemetry use
// So we also check for sampled the same way they do.
const { traceFlags } = span.spanContext();
Expand Down

0 comments on commit 10337a5

Please sign in to comment.