Skip to content

Commit 68fa29e

Browse files
authored
ref: Refactor span status handling to be OTEL compatible (#10871)
This aligns the schema for `span.setStatus()` with the OTEL span schema. Since we still rely, internally, that the status is a string, we now handle this by using the `message` on the status, if set, to send this. We can internally look at changing this, but this should be OK for now.
1 parent 2e8b662 commit 68fa29e

File tree

32 files changed

+228
-222
lines changed

32 files changed

+228
-222
lines changed

packages/browser/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ export {
8080
ModuleMetadata,
8181
moduleMetadataIntegration,
8282
} from '@sentry/core';
83-
export type { SpanStatusType } from '@sentry/core';
8483
export type { Span } from '@sentry/types';
8584
export { makeBrowserOfflineTransport } from './transports/offline';
8685
export { browserProfilingIntegration } from './profiling/integration';

packages/bun/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ export {
8585
captureSession,
8686
endSession,
8787
} from '@sentry/core';
88-
export type { SpanStatusType } from '@sentry/core';
8988
export {
9089
DEFAULT_USER_INCLUDES,
9190
autoDiscoverNodePerformanceMonitoringIntegrations,

packages/core/src/tracing/errors.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
} from '@sentry/utils';
66

77
import { DEBUG_BUILD } from '../debug-build';
8-
import type { SpanStatusType } from './spanstatus';
8+
import { SPAN_STATUS_ERROR } from './spanstatus';
99
import { getActiveTransaction } from './utils';
1010

1111
let errorsInstrumented = false;
@@ -35,9 +35,9 @@ function errorCallback(): void {
3535
// eslint-disable-next-line deprecation/deprecation
3636
const activeTransaction = getActiveTransaction();
3737
if (activeTransaction) {
38-
const status: SpanStatusType = 'internal_error';
39-
DEBUG_BUILD && logger.log(`[Tracing] Transaction: ${status} -> Global error occured`);
40-
activeTransaction.setStatus(status);
38+
const message = 'internal_error';
39+
DEBUG_BUILD && logger.log(`[Tracing] Transaction: ${message} -> Global error occured`);
40+
activeTransaction.setStatus({ code: SPAN_STATUS_ERROR, message });
4141
}
4242
}
4343

packages/core/src/tracing/idletransaction.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { DEBUG_BUILD } from '../debug-build';
55
import { spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils';
66
import type { SentrySpan } from './sentrySpan';
77
import { SpanRecorder } from './sentrySpan';
8+
import { SPAN_STATUS_ERROR } from './spanstatus';
89
import { Transaction } from './transaction';
910

1011
export const TRACING_DEFAULTS = {
@@ -147,7 +148,7 @@ export class IdleTransaction extends Transaction {
147148

148149
setTimeout(() => {
149150
if (!this._finished) {
150-
this.setStatus('deadline_exceeded');
151+
this.setStatus({ code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' });
151152
this._finishReason = IDLE_TRANSACTION_FINISH_REASONS[3];
152153
this.end();
153154
}
@@ -185,7 +186,7 @@ export class IdleTransaction extends Transaction {
185186

186187
// We cancel all pending spans with status "cancelled" to indicate the idle transaction was finished early
187188
if (!spanToJSON(span).timestamp) {
188-
span.setStatus('cancelled');
189+
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'cancelled' });
189190
span.end(endTimestampInS);
190191
DEBUG_BUILD &&
191192
logger.log('[Tracing] cancelling span since transaction ended early', JSON.stringify(span, undefined, 2));
@@ -396,7 +397,7 @@ export class IdleTransaction extends Transaction {
396397
if (this._heartbeatCounter >= 3) {
397398
if (this._autoFinishAllowed) {
398399
DEBUG_BUILD && logger.log('[Tracing] Transaction finished because of no change for 3 heart beats');
399-
this.setStatus('deadline_exceeded');
400+
this.setStatus({ code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' });
400401
this._finishReason = IDLE_TRANSACTION_FINISH_REASONS[0];
401402
this.end();
402403
}

packages/core/src/tracing/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@ export { SentrySpan } from './sentrySpan';
55
export { Transaction } from './transaction';
66
// eslint-disable-next-line deprecation/deprecation
77
export { getActiveTransaction, getActiveSpan } from './utils';
8-
// eslint-disable-next-line deprecation/deprecation
9-
export { SpanStatus } from './spanstatus';
108
export {
119
setHttpStatus,
1210
getSpanStatusFromHttpCode,
1311
} from './spanstatus';
14-
export type { SpanStatusType } from './spanstatus';
12+
export { SPAN_STATUS_ERROR, SPAN_STATUS_OK, SPAN_STATUS_UNSET } from './spanstatus';
1513
export {
1614
startSpan,
1715
startInactiveSpan,

packages/core/src/tracing/sentrySpan.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
SpanContextData,
88
SpanJSON,
99
SpanOrigin,
10+
SpanStatus,
1011
SpanTimeInput,
1112
TraceContext,
1213
Transaction,
@@ -24,7 +25,7 @@ import {
2425
spanToJSON,
2526
spanToTraceContext,
2627
} from '../utils/spanUtils';
27-
import type { SpanStatusType } from './spanstatus';
28+
import { SPAN_STATUS_OK, SPAN_STATUS_UNSET } from './spanstatus';
2829
import { addChildSpanToSpan } from './utils';
2930

3031
/**
@@ -99,7 +100,7 @@ export class SentrySpan implements Span {
99100
/** Epoch timestamp in seconds when the span ended. */
100101
protected _endTime?: number | undefined;
101102
/** Internal keeper of the status */
102-
protected _status?: SpanStatusType | string | undefined;
103+
protected _status?: SpanStatus;
103104

104105
private _logMessage?: string;
105106

@@ -363,7 +364,7 @@ export class SentrySpan implements Span {
363364
/**
364365
* @inheritDoc
365366
*/
366-
public setStatus(value: SpanStatusType): this {
367+
public setStatus(value: SpanStatus): this {
367368
this._status = value;
368369
return this;
369370
}
@@ -445,7 +446,7 @@ export class SentrySpan implements Span {
445446
parent_span_id: this._parentSpanId,
446447
span_id: this._spanId,
447448
start_timestamp: this._startTime,
448-
status: this._status,
449+
status: getStatusMessage(this._status),
449450
// eslint-disable-next-line deprecation/deprecation
450451
tags: Object.keys(this.tags).length > 0 ? this.tags : undefined,
451452
timestamp: this._endTime,
@@ -499,3 +500,15 @@ export class SentrySpan implements Span {
499500
return hasData ? data : attributes;
500501
}
501502
}
503+
504+
function getStatusMessage(status: SpanStatus | undefined): string | undefined {
505+
if (!status || status.code === SPAN_STATUS_UNSET) {
506+
return undefined;
507+
}
508+
509+
if (status.code === SPAN_STATUS_OK) {
510+
return 'ok';
511+
}
512+
513+
return status.message || 'unknown_error';
514+
}
Lines changed: 20 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,53 @@
1-
import type { Span } from '@sentry/types';
1+
import type { Span, SpanStatus } from '@sentry/types';
22

3-
/** The status of an Span.
4-
*
5-
* @deprecated Use string literals - if you require type casting, cast to SpanStatusType type
6-
*/
7-
export enum SpanStatus {
8-
/** The operation completed successfully. */
9-
Ok = 'ok',
10-
/** Deadline expired before operation could complete. */
11-
DeadlineExceeded = 'deadline_exceeded',
12-
/** 401 Unauthorized (actually does mean unauthenticated according to RFC 7235) */
13-
Unauthenticated = 'unauthenticated',
14-
/** 403 Forbidden */
15-
PermissionDenied = 'permission_denied',
16-
/** 404 Not Found. Some requested entity (file or directory) was not found. */
17-
NotFound = 'not_found',
18-
/** 429 Too Many Requests */
19-
ResourceExhausted = 'resource_exhausted',
20-
/** Client specified an invalid argument. 4xx. */
21-
InvalidArgument = 'invalid_argument',
22-
/** 501 Not Implemented */
23-
Unimplemented = 'unimplemented',
24-
/** 503 Service Unavailable */
25-
Unavailable = 'unavailable',
26-
/** Other/generic 5xx. */
27-
InternalError = 'internal_error',
28-
/** Unknown. Any non-standard HTTP status code. */
29-
UnknownError = 'unknown_error',
30-
/** The operation was cancelled (typically by the user). */
31-
Cancelled = 'cancelled',
32-
/** Already exists (409) */
33-
AlreadyExists = 'already_exists',
34-
/** Operation was rejected because the system is not in a state required for the operation's */
35-
FailedPrecondition = 'failed_precondition',
36-
/** The operation was aborted, typically due to a concurrency issue. */
37-
Aborted = 'aborted',
38-
/** Operation was attempted past the valid range. */
39-
OutOfRange = 'out_of_range',
40-
/** Unrecoverable data loss or corruption */
41-
DataLoss = 'data_loss',
42-
}
43-
44-
export type SpanStatusType =
45-
/** The operation completed successfully. */
46-
| 'ok'
47-
/** Deadline expired before operation could complete. */
48-
| 'deadline_exceeded'
49-
/** 401 Unauthorized (actually does mean unauthenticated according to RFC 7235) */
50-
| 'unauthenticated'
51-
/** 403 Forbidden */
52-
| 'permission_denied'
53-
/** 404 Not Found. Some requested entity (file or directory) was not found. */
54-
| 'not_found'
55-
/** 429 Too Many Requests */
56-
| 'resource_exhausted'
57-
/** Client specified an invalid argument. 4xx. */
58-
| 'invalid_argument'
59-
/** 501 Not Implemented */
60-
| 'unimplemented'
61-
/** 503 Service Unavailable */
62-
| 'unavailable'
63-
/** Other/generic 5xx. */
64-
| 'internal_error'
65-
/** Unknown. Any non-standard HTTP status code. */
66-
| 'unknown_error'
67-
/** The operation was cancelled (typically by the user). */
68-
| 'cancelled'
69-
/** Already exists (409) */
70-
| 'already_exists'
71-
/** Operation was rejected because the system is not in a state required for the operation's */
72-
| 'failed_precondition'
73-
/** The operation was aborted, typically due to a concurrency issue. */
74-
| 'aborted'
75-
/** Operation was attempted past the valid range. */
76-
| 'out_of_range'
77-
/** Unrecoverable data loss or corruption */
78-
| 'data_loss';
3+
export const SPAN_STATUS_UNSET = 0;
4+
export const SPAN_STATUS_OK = 1;
5+
export const SPAN_STATUS_ERROR = 2;
796

807
/**
81-
* Converts a HTTP status code into a {@link SpanStatusType}.
8+
* Converts a HTTP status code into a sentry status with a message.
829
*
8310
* @param httpStatus The HTTP response status code.
8411
* @returns The span status or unknown_error.
8512
*/
86-
export function getSpanStatusFromHttpCode(httpStatus: number): SpanStatusType {
13+
export function getSpanStatusFromHttpCode(httpStatus: number): SpanStatus {
8714
if (httpStatus < 400 && httpStatus >= 100) {
88-
return 'ok';
15+
return { code: SPAN_STATUS_OK };
8916
}
9017

9118
if (httpStatus >= 400 && httpStatus < 500) {
9219
switch (httpStatus) {
9320
case 401:
94-
return 'unauthenticated';
21+
return { code: SPAN_STATUS_ERROR, message: 'unauthenticated' };
9522
case 403:
96-
return 'permission_denied';
23+
return { code: SPAN_STATUS_ERROR, message: 'permission_denied' };
9724
case 404:
98-
return 'not_found';
25+
return { code: SPAN_STATUS_ERROR, message: 'not_found' };
9926
case 409:
100-
return 'already_exists';
27+
return { code: SPAN_STATUS_ERROR, message: 'already_exists' };
10128
case 413:
102-
return 'failed_precondition';
29+
return { code: SPAN_STATUS_ERROR, message: 'failed_precondition' };
10330
case 429:
104-
return 'resource_exhausted';
31+
return { code: SPAN_STATUS_ERROR, message: 'resource_exhausted' };
10532
default:
106-
return 'invalid_argument';
33+
return { code: SPAN_STATUS_ERROR, message: 'invalid_argument' };
10734
}
10835
}
10936

11037
if (httpStatus >= 500 && httpStatus < 600) {
11138
switch (httpStatus) {
11239
case 501:
113-
return 'unimplemented';
40+
return { code: SPAN_STATUS_ERROR, message: 'unimplemented' };
11441
case 503:
115-
return 'unavailable';
42+
return { code: SPAN_STATUS_ERROR, message: 'unavailable' };
11643
case 504:
117-
return 'deadline_exceeded';
44+
return { code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' };
11845
default:
119-
return 'internal_error';
46+
return { code: SPAN_STATUS_ERROR, message: 'internal_error' };
12047
}
12148
}
12249

123-
return 'unknown_error';
50+
return { code: SPAN_STATUS_ERROR, message: 'unknown_error' };
12451
}
12552

12653
/**
@@ -131,7 +58,7 @@ export function setHttpStatus(span: Span, httpStatus: number): void {
13158
span.setAttribute('http.response.status_code', httpStatus);
13259

13360
const spanStatus = getSpanStatusFromHttpCode(httpStatus);
134-
if (spanStatus !== 'unknown_error') {
61+
if (spanStatus.message !== 'unknown_error') {
13562
span.setStatus(spanStatus);
13663
}
13764
}

packages/core/src/tracing/trace.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { hasTracingEnabled } from '../utils/hasTracingEnabled';
1010
import { spanIsSampled, spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils';
1111
import { getDynamicSamplingContextFromSpan } from './dynamicSamplingContext';
1212
import type { SentrySpan } from './sentrySpan';
13+
import { SPAN_STATUS_ERROR } from './spanstatus';
1314
import { addChildSpanToSpan, getActiveSpan, setCapturedScopesOnSpan } from './utils';
1415

1516
/**
@@ -54,7 +55,7 @@ export function startSpan<T>(context: StartSpanOptions, callback: (span: Span |
5455
if (activeSpan) {
5556
const { status } = spanToJSON(activeSpan);
5657
if (!status || status === 'ok') {
57-
activeSpan.setStatus('internal_error');
58+
activeSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
5859
}
5960
}
6061
},
@@ -112,7 +113,7 @@ export function startSpanManual<T>(
112113
if (activeSpan && activeSpan.isRecording()) {
113114
const { status } = spanToJSON(activeSpan);
114115
if (!status || status === 'ok') {
115-
activeSpan.setStatus('internal_error');
116+
activeSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
116117
}
117118
}
118119
},

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { timestampInSeconds } from '@sentry/utils';
22
import { SentrySpan } from '../../../src';
3+
import { SPAN_STATUS_ERROR } from '../../../src/tracing/spanstatus';
34
import { TRACE_FLAG_NONE, TRACE_FLAG_SAMPLED, spanToJSON, spanToTraceContext } from '../../../src/utils/spanUtils';
45

56
describe('span', () => {
@@ -42,7 +43,7 @@ describe('span', () => {
4243
describe('status', () => {
4344
test('setStatus', () => {
4445
const span = new SentrySpan({});
45-
span.setStatus('permission_denied');
46+
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'permission_denied' });
4647
expect(spanToTraceContext(span).status).toBe('permission_denied');
4748
});
4849
});

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { TRACEPARENT_REGEXP, timestampInSeconds } from '@sentry/utils';
2-
import { SentrySpan, spanToTraceHeader } from '../../../src';
2+
import { SPAN_STATUS_OK, SentrySpan, spanToTraceHeader } from '../../../src';
33
import { spanIsSampled, spanTimeInputToSeconds, spanToJSON } from '../../../src/utils/spanUtils';
44

55
describe('spanToTraceHeader', () => {
@@ -72,7 +72,7 @@ describe('spanToJSON', () => {
7272
startTimestamp: 123,
7373
endTimestamp: 456,
7474
});
75-
span.setStatus('ok');
75+
span.setStatus({ code: SPAN_STATUS_OK });
7676

7777
expect(spanToJSON(span)).toEqual({
7878
description: 'test name',

packages/deno/src/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@ export {
8686
endSession,
8787
} from '@sentry/core';
8888

89-
export type { SpanStatusType } from '@sentry/core';
90-
9189
export { DenoClient } from './client';
9290

9391
export {

packages/nextjs/src/common/utils/edgeWrapperUtils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
33
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
4+
SPAN_STATUS_OK,
45
addTracingExtensions,
56
captureException,
67
continueTrace,
@@ -70,7 +71,7 @@ export function withEdgeWrapping<H extends EdgeRouteHandler>(
7071
if (handlerResult instanceof Response) {
7172
setHttpStatus(span, handlerResult.status);
7273
} else {
73-
span.setStatus('ok');
74+
span.setStatus({ code: SPAN_STATUS_OK });
7475
}
7576
}
7677

0 commit comments

Comments
 (0)