Skip to content

ref: Refactor span status handling to be OTEL compatible #10871

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 4, 2024
Merged
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
1 change: 0 additions & 1 deletion packages/browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ export {
ModuleMetadata,
moduleMetadataIntegration,
} from '@sentry/core';
export type { SpanStatusType } from '@sentry/core';
export type { Span } from '@sentry/types';
export { makeBrowserOfflineTransport } from './transports/offline';
export { browserProfilingIntegration } from './profiling/integration';
1 change: 0 additions & 1 deletion packages/bun/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ export {
captureSession,
endSession,
} from '@sentry/core';
export type { SpanStatusType } from '@sentry/core';
export {
DEFAULT_USER_INCLUDES,
autoDiscoverNodePerformanceMonitoringIntegrations,
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/tracing/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from '@sentry/utils';

import { DEBUG_BUILD } from '../debug-build';
import type { SpanStatusType } from './spanstatus';
import { SPAN_STATUS_ERROR } from './spanstatus';
import { getActiveTransaction } from './utils';

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

Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/tracing/idletransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DEBUG_BUILD } from '../debug-build';
import { spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils';
import type { SentrySpan } from './sentrySpan';
import { SpanRecorder } from './sentrySpan';
import { SPAN_STATUS_ERROR } from './spanstatus';
import { Transaction } from './transaction';

export const TRACING_DEFAULTS = {
Expand Down Expand Up @@ -147,7 +148,7 @@ export class IdleTransaction extends Transaction {

setTimeout(() => {
if (!this._finished) {
this.setStatus('deadline_exceeded');
this.setStatus({ code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' });
this._finishReason = IDLE_TRANSACTION_FINISH_REASONS[3];
this.end();
}
Expand Down Expand Up @@ -185,7 +186,7 @@ export class IdleTransaction extends Transaction {

// We cancel all pending spans with status "cancelled" to indicate the idle transaction was finished early
if (!spanToJSON(span).timestamp) {
span.setStatus('cancelled');
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'cancelled' });
span.end(endTimestampInS);
DEBUG_BUILD &&
logger.log('[Tracing] cancelling span since transaction ended early', JSON.stringify(span, undefined, 2));
Expand Down Expand Up @@ -396,7 +397,7 @@ export class IdleTransaction extends Transaction {
if (this._heartbeatCounter >= 3) {
if (this._autoFinishAllowed) {
DEBUG_BUILD && logger.log('[Tracing] Transaction finished because of no change for 3 heart beats');
this.setStatus('deadline_exceeded');
this.setStatus({ code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' });
this._finishReason = IDLE_TRANSACTION_FINISH_REASONS[0];
this.end();
}
Expand Down
4 changes: 1 addition & 3 deletions packages/core/src/tracing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ export { SentrySpan } from './sentrySpan';
export { Transaction } from './transaction';
// eslint-disable-next-line deprecation/deprecation
export { getActiveTransaction, getActiveSpan } from './utils';
// eslint-disable-next-line deprecation/deprecation
export { SpanStatus } from './spanstatus';
export {
setHttpStatus,
getSpanStatusFromHttpCode,
} from './spanstatus';
export type { SpanStatusType } from './spanstatus';
export { SPAN_STATUS_ERROR, SPAN_STATUS_OK, SPAN_STATUS_UNSET } from './spanstatus';
export {
startSpan,
startInactiveSpan,
Expand Down
21 changes: 17 additions & 4 deletions packages/core/src/tracing/sentrySpan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
SpanContextData,
SpanJSON,
SpanOrigin,
SpanStatus,
SpanTimeInput,
TraceContext,
Transaction,
Expand All @@ -24,7 +25,7 @@ import {
spanToJSON,
spanToTraceContext,
} from '../utils/spanUtils';
import type { SpanStatusType } from './spanstatus';
import { SPAN_STATUS_OK, SPAN_STATUS_UNSET } from './spanstatus';
import { addChildSpanToSpan } from './utils';

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

private _logMessage?: string;

Expand Down Expand Up @@ -363,7 +364,7 @@ export class SentrySpan implements Span {
/**
* @inheritDoc
*/
public setStatus(value: SpanStatusType): this {
public setStatus(value: SpanStatus): this {
this._status = value;
return this;
}
Expand Down Expand Up @@ -445,7 +446,7 @@ export class SentrySpan implements Span {
parent_span_id: this._parentSpanId,
span_id: this._spanId,
start_timestamp: this._startTime,
status: this._status,
status: getStatusMessage(this._status),
// eslint-disable-next-line deprecation/deprecation
tags: Object.keys(this.tags).length > 0 ? this.tags : undefined,
timestamp: this._endTime,
Expand Down Expand Up @@ -499,3 +500,15 @@ export class SentrySpan implements Span {
return hasData ? data : attributes;
}
}

function getStatusMessage(status: SpanStatus | undefined): string | undefined {
if (!status || status.code === SPAN_STATUS_UNSET) {
return undefined;
}

if (status.code === SPAN_STATUS_OK) {
return 'ok';
}

return status.message || 'unknown_error';
}
113 changes: 20 additions & 93 deletions packages/core/src/tracing/spanstatus.ts
Original file line number Diff line number Diff line change
@@ -1,126 +1,53 @@
import type { Span } from '@sentry/types';
import type { Span, SpanStatus } from '@sentry/types';

/** The status of an Span.
*
* @deprecated Use string literals - if you require type casting, cast to SpanStatusType type
*/
export enum SpanStatus {
/** The operation completed successfully. */
Ok = 'ok',
/** Deadline expired before operation could complete. */
DeadlineExceeded = 'deadline_exceeded',
/** 401 Unauthorized (actually does mean unauthenticated according to RFC 7235) */
Unauthenticated = 'unauthenticated',
/** 403 Forbidden */
PermissionDenied = 'permission_denied',
/** 404 Not Found. Some requested entity (file or directory) was not found. */
NotFound = 'not_found',
/** 429 Too Many Requests */
ResourceExhausted = 'resource_exhausted',
/** Client specified an invalid argument. 4xx. */
InvalidArgument = 'invalid_argument',
/** 501 Not Implemented */
Unimplemented = 'unimplemented',
/** 503 Service Unavailable */
Unavailable = 'unavailable',
/** Other/generic 5xx. */
InternalError = 'internal_error',
/** Unknown. Any non-standard HTTP status code. */
UnknownError = 'unknown_error',
/** The operation was cancelled (typically by the user). */
Cancelled = 'cancelled',
/** Already exists (409) */
AlreadyExists = 'already_exists',
/** Operation was rejected because the system is not in a state required for the operation's */
FailedPrecondition = 'failed_precondition',
/** The operation was aborted, typically due to a concurrency issue. */
Aborted = 'aborted',
/** Operation was attempted past the valid range. */
OutOfRange = 'out_of_range',
/** Unrecoverable data loss or corruption */
DataLoss = 'data_loss',
}

export type SpanStatusType =
/** The operation completed successfully. */
| 'ok'
/** Deadline expired before operation could complete. */
| 'deadline_exceeded'
/** 401 Unauthorized (actually does mean unauthenticated according to RFC 7235) */
| 'unauthenticated'
/** 403 Forbidden */
| 'permission_denied'
/** 404 Not Found. Some requested entity (file or directory) was not found. */
| 'not_found'
/** 429 Too Many Requests */
| 'resource_exhausted'
/** Client specified an invalid argument. 4xx. */
| 'invalid_argument'
/** 501 Not Implemented */
| 'unimplemented'
/** 503 Service Unavailable */
| 'unavailable'
/** Other/generic 5xx. */
| 'internal_error'
/** Unknown. Any non-standard HTTP status code. */
| 'unknown_error'
/** The operation was cancelled (typically by the user). */
| 'cancelled'
/** Already exists (409) */
| 'already_exists'
/** Operation was rejected because the system is not in a state required for the operation's */
| 'failed_precondition'
/** The operation was aborted, typically due to a concurrency issue. */
| 'aborted'
/** Operation was attempted past the valid range. */
| 'out_of_range'
/** Unrecoverable data loss or corruption */
| 'data_loss';
export const SPAN_STATUS_UNSET = 0;
export const SPAN_STATUS_OK = 1;
export const SPAN_STATUS_ERROR = 2;

/**
* Converts a HTTP status code into a {@link SpanStatusType}.
* Converts a HTTP status code into a sentry status with a message.
*
* @param httpStatus The HTTP response status code.
* @returns The span status or unknown_error.
*/
export function getSpanStatusFromHttpCode(httpStatus: number): SpanStatusType {
export function getSpanStatusFromHttpCode(httpStatus: number): SpanStatus {
if (httpStatus < 400 && httpStatus >= 100) {
return 'ok';
return { code: SPAN_STATUS_OK };
}

if (httpStatus >= 400 && httpStatus < 500) {
switch (httpStatus) {
case 401:
return 'unauthenticated';
return { code: SPAN_STATUS_ERROR, message: 'unauthenticated' };
case 403:
return 'permission_denied';
return { code: SPAN_STATUS_ERROR, message: 'permission_denied' };
case 404:
return 'not_found';
return { code: SPAN_STATUS_ERROR, message: 'not_found' };
case 409:
return 'already_exists';
return { code: SPAN_STATUS_ERROR, message: 'already_exists' };
case 413:
return 'failed_precondition';
return { code: SPAN_STATUS_ERROR, message: 'failed_precondition' };
case 429:
return 'resource_exhausted';
return { code: SPAN_STATUS_ERROR, message: 'resource_exhausted' };
default:
return 'invalid_argument';
return { code: SPAN_STATUS_ERROR, message: 'invalid_argument' };
}
}

if (httpStatus >= 500 && httpStatus < 600) {
switch (httpStatus) {
case 501:
return 'unimplemented';
return { code: SPAN_STATUS_ERROR, message: 'unimplemented' };
case 503:
return 'unavailable';
return { code: SPAN_STATUS_ERROR, message: 'unavailable' };
case 504:
return 'deadline_exceeded';
return { code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' };
default:
return 'internal_error';
return { code: SPAN_STATUS_ERROR, message: 'internal_error' };
}
}

return 'unknown_error';
return { code: SPAN_STATUS_ERROR, message: 'unknown_error' };
}

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

const spanStatus = getSpanStatusFromHttpCode(httpStatus);
if (spanStatus !== 'unknown_error') {
if (spanStatus.message !== 'unknown_error') {
span.setStatus(spanStatus);
}
}
5 changes: 3 additions & 2 deletions packages/core/src/tracing/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { hasTracingEnabled } from '../utils/hasTracingEnabled';
import { spanIsSampled, spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils';
import { getDynamicSamplingContextFromSpan } from './dynamicSamplingContext';
import type { SentrySpan } from './sentrySpan';
import { SPAN_STATUS_ERROR } from './spanstatus';
import { addChildSpanToSpan, getActiveSpan, setCapturedScopesOnSpan } from './utils';

/**
Expand Down Expand Up @@ -49,7 +50,7 @@ export function startSpan<T>(context: StartSpanOptions, callback: (span: Span |
if (activeSpan) {
const { status } = spanToJSON(activeSpan);
if (!status || status === 'ok') {
activeSpan.setStatus('internal_error');
activeSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
}
}
},
Expand Down Expand Up @@ -102,7 +103,7 @@ export function startSpanManual<T>(
if (activeSpan && activeSpan.isRecording()) {
const { status } = spanToJSON(activeSpan);
if (!status || status === 'ok') {
activeSpan.setStatus('internal_error');
activeSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion packages/core/test/lib/tracing/span.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { timestampInSeconds } from '@sentry/utils';
import { SentrySpan } from '../../../src';
import { SPAN_STATUS_ERROR } from '../../../src/tracing/spanstatus';
import { TRACE_FLAG_NONE, TRACE_FLAG_SAMPLED, spanToJSON, spanToTraceContext } from '../../../src/utils/spanUtils';

describe('span', () => {
Expand Down Expand Up @@ -42,7 +43,7 @@ describe('span', () => {
describe('status', () => {
test('setStatus', () => {
const span = new SentrySpan({});
span.setStatus('permission_denied');
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'permission_denied' });
expect(spanToTraceContext(span).status).toBe('permission_denied');
});
});
Expand Down
4 changes: 2 additions & 2 deletions packages/core/test/lib/utils/spanUtils.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TRACEPARENT_REGEXP, timestampInSeconds } from '@sentry/utils';
import { SentrySpan, spanToTraceHeader } from '../../../src';
import { SPAN_STATUS_OK, SentrySpan, spanToTraceHeader } from '../../../src';
import { spanIsSampled, spanTimeInputToSeconds, spanToJSON } from '../../../src/utils/spanUtils';

describe('spanToTraceHeader', () => {
Expand Down Expand Up @@ -72,7 +72,7 @@ describe('spanToJSON', () => {
startTimestamp: 123,
endTimestamp: 456,
});
span.setStatus('ok');
span.setStatus({ code: SPAN_STATUS_OK });

expect(spanToJSON(span)).toEqual({
description: 'test name',
Expand Down
2 changes: 0 additions & 2 deletions packages/deno/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ export {
endSession,
} from '@sentry/core';

export type { SpanStatusType } from '@sentry/core';

export { DenoClient } from './client';

export {
Expand Down
3 changes: 2 additions & 1 deletion packages/nextjs/src/common/utils/edgeWrapperUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
SPAN_STATUS_OK,
addTracingExtensions,
captureException,
continueTrace,
Expand Down Expand Up @@ -70,7 +71,7 @@ export function withEdgeWrapping<H extends EdgeRouteHandler>(
if (handlerResult instanceof Response) {
setHttpStatus(span, handlerResult.status);
} else {
span.setStatus('ok');
span.setStatus({ code: SPAN_STATUS_OK });
}
}

Expand Down
Loading