Skip to content

Commit c5b1f0c

Browse files
committed
feat(core): Streamline SpanJSON type
1 parent 8b81476 commit c5b1f0c

File tree

6 files changed

+41
-40
lines changed

6 files changed

+41
-40
lines changed

biome.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"**/fixtures/*/*.json",
4040
"**/*.min.js",
4141
".next/**",
42+
".nuxt/**",
4243
".svelte-kit/**",
4344
".angular/**",
4445
"angular.json",

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export type SpanTimeInput = HrTime | number | Date;
4444

4545
/** A JSON representation of a span. */
4646
export interface SpanJSON {
47-
data?: { [key: string]: any };
47+
data: SpanAttributes;
4848
description?: string;
4949
op?: string;
5050
parent_span_id?: string;

packages/core/src/utils/spanUtils.ts

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -112,42 +112,41 @@ function ensureTimestampInSeconds(timestamp: number): number {
112112
// Note: Because of this, we currently have a circular type dependency (which we opted out of in package.json).
113113
// This is not avoidable as we need `spanToJSON` in `spanUtils.ts`, which in turn is needed by `span.ts` for backwards compatibility.
114114
// And `spanToJSON` needs the Span class from `span.ts` to check here.
115-
export function spanToJSON(span: Span): Partial<SpanJSON> {
115+
export function spanToJSON(span: Span): SpanJSON {
116116
if (spanIsSentrySpan(span)) {
117117
return span.getSpanJSON();
118118
}
119119

120-
try {
121-
const { spanId: span_id, traceId: trace_id } = span.spanContext();
122-
123-
// Handle a span from @opentelemetry/sdk-base-trace's `Span` class
124-
if (spanIsOpenTelemetrySdkTraceBaseSpan(span)) {
125-
const { attributes, startTime, name, endTime, parentSpanId, status } = span;
126-
127-
return dropUndefinedKeys({
128-
span_id,
129-
trace_id,
130-
data: attributes,
131-
description: name,
132-
parent_span_id: parentSpanId,
133-
start_timestamp: spanTimeInputToSeconds(startTime),
134-
// This is [0,0] by default in OTEL, in which case we want to interpret this as no end time
135-
timestamp: spanTimeInputToSeconds(endTime) || undefined,
136-
status: getStatusMessage(status),
137-
op: attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP],
138-
origin: attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] as SpanOrigin | undefined,
139-
_metrics_summary: getMetricSummaryJsonForSpan(span),
140-
});
141-
}
120+
const { spanId: span_id, traceId: trace_id } = span.spanContext();
142121

143-
// Finally, at least we have `spanContext()`....
144-
return {
122+
// Handle a span from @opentelemetry/sdk-base-trace's `Span` class
123+
if (spanIsOpenTelemetrySdkTraceBaseSpan(span)) {
124+
const { attributes, startTime, name, endTime, parentSpanId, status } = span;
125+
126+
return dropUndefinedKeys({
145127
span_id,
146128
trace_id,
147-
};
148-
} catch {
149-
return {};
129+
data: attributes,
130+
description: name,
131+
parent_span_id: parentSpanId,
132+
start_timestamp: spanTimeInputToSeconds(startTime),
133+
// This is [0,0] by default in OTEL, in which case we want to interpret this as no end time
134+
timestamp: spanTimeInputToSeconds(endTime) || undefined,
135+
status: getStatusMessage(status),
136+
op: attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP],
137+
origin: attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] as SpanOrigin | undefined,
138+
_metrics_summary: getMetricSummaryJsonForSpan(span),
139+
});
150140
}
141+
142+
// Finally, at least we have `spanContext()`....
143+
// This should not actually happen in reality, but we need to handle it for type safety.
144+
return {
145+
span_id,
146+
trace_id,
147+
start_timestamp: timestampInSeconds(),
148+
data: {},
149+
};
151150
}
152151

153152
function spanIsOpenTelemetrySdkTraceBaseSpan(span: Span): span is OpenTelemetrySdkTraceBaseSpan {

packages/node/src/integrations/tracing/graphql.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ export const instrumentGraphql = generateInstrumentOnce<GraphqlOptions>(
6767
// We keep track of each operation on the root span
6868
// This can either be a string, or an array of strings (if there are multiple operations)
6969
if (Array.isArray(existingOperations)) {
70-
existingOperations.push(newOperation);
70+
(existingOperations as string[]).push(newOperation);
7171
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION, existingOperations);
72-
} else if (existingOperations) {
72+
} else if (typeof existingOperations === 'string') {
7373
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION, [existingOperations, newOperation]);
7474
} else {
7575
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION, newOperation);

packages/node/src/integrations/tracing/vercelai/index.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ const _vercelAIIntegration = (() => {
3232
span.data['ai.prompt_tokens.used'] = attributes['ai.usage.promptTokens'];
3333
}
3434
if (
35-
attributes['ai.usage.completionTokens'] != undefined &&
36-
attributes['ai.usage.promptTokens'] != undefined
35+
typeof attributes['ai.usage.completionTokens'] == 'number' &&
36+
typeof attributes['ai.usage.promptTokens'] == 'number'
3737
) {
3838
span.data['ai.total_tokens.used'] =
3939
attributes['ai.usage.completionTokens'] + attributes['ai.usage.promptTokens'];
@@ -56,13 +56,13 @@ const _vercelAIIntegration = (() => {
5656
}
5757

5858
// The id of the model
59-
const aiModelId: string | undefined = attributes['ai.model.id'];
59+
const aiModelId = attributes['ai.model.id'];
6060

6161
// the provider of the model
62-
const aiModelProvider: string | undefined = attributes['ai.model.provider'];
62+
const aiModelProvider = attributes['ai.model.provider'];
6363

6464
// both of these must be defined for the integration to work
65-
if (!aiModelId || !aiModelProvider) {
65+
if (typeof aiModelId !== 'string' || typeof aiModelProvider !== 'string' || !aiModelId || !aiModelProvider) {
6666
return;
6767
}
6868

@@ -137,9 +137,10 @@ const _vercelAIIntegration = (() => {
137137
span.updateName(nameWthoutAi);
138138

139139
// If a Telemetry name is set and it is a pipeline span, use that as the operation name
140-
if (attributes['ai.telemetry.functionId'] && isPipelineSpan) {
141-
span.updateName(attributes['ai.telemetry.functionId']);
142-
span.setAttribute('ai.pipeline.name', attributes['ai.telemetry.functionId']);
140+
const functionId = attributes['ai.telemetry.functionId'];
141+
if (functionId && typeof functionId === 'string' && isPipelineSpan) {
142+
span.updateName(functionId);
143+
span.setAttribute('ai.pipeline.name', functionId);
143144
}
144145

145146
if (attributes['ai.prompt']) {

packages/opentelemetry/src/propagator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ function getCurrentURL(span: Span): string | undefined {
316316
// `ATTR_URL_FULL` is the new attribute, but we still support the old one, `SEMATTRS_HTTP_URL`, for now.
317317
// eslint-disable-next-line deprecation/deprecation
318318
const urlAttribute = spanData?.[SEMATTRS_HTTP_URL] || spanData?.[ATTR_URL_FULL];
319-
if (urlAttribute) {
319+
if (typeof urlAttribute === 'string') {
320320
return urlAttribute;
321321
}
322322

0 commit comments

Comments
 (0)