diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index d4c47e6f91eb..91c02de4ed7c 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -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'; diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index feb114d4723f..b4491a13cb1f 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -85,7 +85,6 @@ export { captureSession, endSession, } from '@sentry/core'; -export type { SpanStatusType } from '@sentry/core'; export { DEFAULT_USER_INCLUDES, autoDiscoverNodePerformanceMonitoringIntegrations, diff --git a/packages/core/src/tracing/errors.ts b/packages/core/src/tracing/errors.ts index f93486129c35..2645c10e42b8 100644 --- a/packages/core/src/tracing/errors.ts +++ b/packages/core/src/tracing/errors.ts @@ -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; @@ -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 }); } } diff --git a/packages/core/src/tracing/idletransaction.ts b/packages/core/src/tracing/idletransaction.ts index 8d0db5d92762..0aad33ca6836 100644 --- a/packages/core/src/tracing/idletransaction.ts +++ b/packages/core/src/tracing/idletransaction.ts @@ -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 = { @@ -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(); } @@ -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)); @@ -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(); } diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts index f8284fe16e29..9ca8f26eac3f 100644 --- a/packages/core/src/tracing/index.ts +++ b/packages/core/src/tracing/index.ts @@ -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, diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index 9d1587d5fb60..b0e82321e33e 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -7,6 +7,7 @@ import type { SpanContextData, SpanJSON, SpanOrigin, + SpanStatus, SpanTimeInput, TraceContext, Transaction, @@ -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'; /** @@ -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; @@ -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; } @@ -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, @@ -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'; +} diff --git a/packages/core/src/tracing/spanstatus.ts b/packages/core/src/tracing/spanstatus.ts index 3855fda0307e..b1fd046f10c3 100644 --- a/packages/core/src/tracing/spanstatus.ts +++ b/packages/core/src/tracing/spanstatus.ts @@ -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' }; } /** @@ -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); } } diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index 561f61ce9265..3ac49ef71bf5 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -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'; /** @@ -49,7 +50,7 @@ export function startSpan(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' }); } } }, @@ -102,7 +103,7 @@ export function startSpanManual( 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' }); } } }, diff --git a/packages/core/test/lib/tracing/span.test.ts b/packages/core/test/lib/tracing/span.test.ts index 0a89b5acc5ff..9edceb86c33d 100644 --- a/packages/core/test/lib/tracing/span.test.ts +++ b/packages/core/test/lib/tracing/span.test.ts @@ -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', () => { @@ -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'); }); }); diff --git a/packages/core/test/lib/utils/spanUtils.test.ts b/packages/core/test/lib/utils/spanUtils.test.ts index 28a8e961c294..56b07405b02c 100644 --- a/packages/core/test/lib/utils/spanUtils.test.ts +++ b/packages/core/test/lib/utils/spanUtils.test.ts @@ -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', () => { @@ -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', diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 50a422f776ab..d0d034dd0ad4 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -86,8 +86,6 @@ export { endSession, } from '@sentry/core'; -export type { SpanStatusType } from '@sentry/core'; - export { DenoClient } from './client'; export { diff --git a/packages/nextjs/src/common/utils/edgeWrapperUtils.ts b/packages/nextjs/src/common/utils/edgeWrapperUtils.ts index 8597228f6e83..be0c56236233 100644 --- a/packages/nextjs/src/common/utils/edgeWrapperUtils.ts +++ b/packages/nextjs/src/common/utils/edgeWrapperUtils.ts @@ -1,6 +1,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + SPAN_STATUS_OK, addTracingExtensions, captureException, continueTrace, @@ -70,7 +71,7 @@ export function withEdgeWrapping( if (handlerResult instanceof Response) { setHttpStatus(span, handlerResult.status); } else { - span.setStatus('ok'); + span.setStatus({ code: SPAN_STATUS_OK }); } } diff --git a/packages/nextjs/src/common/utils/wrapperUtils.ts b/packages/nextjs/src/common/utils/wrapperUtils.ts index 20f458bf6013..5e489fb90c09 100644 --- a/packages/nextjs/src/common/utils/wrapperUtils.ts +++ b/packages/nextjs/src/common/utils/wrapperUtils.ts @@ -2,6 +2,8 @@ import type { IncomingMessage, ServerResponse } from 'http'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + SPAN_STATUS_ERROR, + SPAN_STATUS_OK, captureException, continueTrace, startInactiveSpan, @@ -109,7 +111,7 @@ export function withTracedServerSideDataFetcher Pr }, }); if (requestSpan) { - requestSpan.setStatus('ok'); + requestSpan.setStatus({ code: SPAN_STATUS_OK }); setSpanOnRequest(requestSpan, req); autoEndSpanOnResponseEnd(requestSpan, res); } @@ -126,12 +128,12 @@ export function withTracedServerSideDataFetcher Pr }, }, async dataFetcherSpan => { - dataFetcherSpan?.setStatus('ok'); + dataFetcherSpan?.setStatus({ code: SPAN_STATUS_OK }); try { return await origDataFetcher.apply(this, args); } catch (e) { - dataFetcherSpan?.setStatus('internal_error'); - requestSpan?.setStatus('internal_error'); + dataFetcherSpan?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); + requestSpan?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); throw e; } finally { dataFetcherSpan?.end(); @@ -180,12 +182,12 @@ export async function callDataFetcherTraced Promis }, }, async dataFetcherSpan => { - dataFetcherSpan?.setStatus('ok'); + dataFetcherSpan?.setStatus({ code: SPAN_STATUS_OK }); try { return await origFunction(...origFunctionArgs); } catch (e) { - dataFetcherSpan?.setStatus('internal_error'); + dataFetcherSpan?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); captureException(e, { mechanism: { handled: false } }); throw e; } finally { diff --git a/packages/nextjs/src/common/withServerActionInstrumentation.ts b/packages/nextjs/src/common/withServerActionInstrumentation.ts index 0c54a4f5b665..39c26e4dfca6 100644 --- a/packages/nextjs/src/common/withServerActionInstrumentation.ts +++ b/packages/nextjs/src/common/withServerActionInstrumentation.ts @@ -1,4 +1,4 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getIsolationScope } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SPAN_STATUS_ERROR, getIsolationScope } from '@sentry/core'; import { addTracingExtensions, captureException, @@ -102,11 +102,11 @@ async function withServerActionInstrumentationImplementation { if (isNotFoundNavigationError(error)) { // We don't want to report "not-found"s - span?.setStatus('not_found'); + span?.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); } else if (isRedirectNavigationError(error)) { // Don't do anything for redirects } else { - span?.setStatus('internal_error'); + span?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); captureException(error, { mechanism: { handled: false, diff --git a/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts b/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts index d1dbecbaec63..0041d84c9838 100644 --- a/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts +++ b/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts @@ -1,5 +1,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + SPAN_STATUS_ERROR, + SPAN_STATUS_OK, addTracingExtensions, captureException, getClient, @@ -78,12 +80,12 @@ export function wrapGenerationFunctionWithSentry a err => { if (isNotFoundNavigationError(err)) { // We don't want to report "not-found"s - span?.setStatus('not_found'); + span?.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); } else if (isRedirectNavigationError(err)) { // We don't want to report redirects - span?.setStatus('ok'); + span?.setStatus({ code: SPAN_STATUS_OK }); } else { - span?.setStatus('internal_error'); + span?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); captureException(err, { mechanism: { handled: false, diff --git a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts index 7408fa7f39f3..06a72a61ac18 100644 --- a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts +++ b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts @@ -1,5 +1,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + SPAN_STATUS_ERROR, + SPAN_STATUS_OK, addTracingExtensions, captureException, getCurrentScope, @@ -67,12 +69,12 @@ export function wrapServerComponentWithSentry any> error => { if (isNotFoundNavigationError(error)) { // We don't want to report "not-found"s - span?.setStatus('not_found'); + span?.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); } else if (isRedirectNavigationError(error)) { // We don't want to report redirects - span?.setStatus('ok'); + span?.setStatus({ code: SPAN_STATUS_OK }); } else { - span?.setStatus('internal_error'); + span?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); captureException(error, { mechanism: { handled: false, diff --git a/packages/node-experimental/src/integrations/tracing/hapi/index.ts b/packages/node-experimental/src/integrations/tracing/hapi/index.ts index 4d72af191f84..ac3766aaae86 100644 --- a/packages/node-experimental/src/integrations/tracing/hapi/index.ts +++ b/packages/node-experimental/src/integrations/tracing/hapi/index.ts @@ -1,6 +1,13 @@ import { registerInstrumentations } from '@opentelemetry/instrumentation'; import { HapiInstrumentation } from '@opentelemetry/instrumentation-hapi'; -import { SDK_VERSION, captureException, defineIntegration, getActiveSpan, getRootSpan } from '@sentry/core'; +import { + SDK_VERSION, + SPAN_STATUS_ERROR, + captureException, + defineIntegration, + getActiveSpan, + getRootSpan, +} from '@sentry/core'; import type { IntegrationFn } from '@sentry/types'; import type { Boom, RequestEvent, ResponseObject, Server } from './types'; @@ -61,7 +68,7 @@ export const hapiErrorPlugin = { } if (rootSpan) { - rootSpan.setStatus('internal_error'); + rootSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); rootSpan.end(); } }); diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 8f4e1a9ee87c..9be997a9eb11 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -86,8 +86,6 @@ export { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, } from '@sentry/core'; -export type { SpanStatusType } from '@sentry/core'; - export { autoDiscoverNodePerformanceMonitoringIntegrations } from './tracing'; export { NodeClient } from './client'; diff --git a/packages/node/src/integrations/hapi/index.ts b/packages/node/src/integrations/hapi/index.ts index 9ea2df9e696c..3af732ce730a 100644 --- a/packages/node/src/integrations/hapi/index.ts +++ b/packages/node/src/integrations/hapi/index.ts @@ -1,5 +1,6 @@ import { SDK_VERSION, + SPAN_STATUS_ERROR, captureException, continueTrace, convertIntegrationFnToClass, @@ -58,7 +59,7 @@ export const hapiErrorPlugin = { } if (transaction) { - transaction.setStatus('internal_error'); + transaction.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); transaction.end(); } }); diff --git a/packages/node/src/integrations/undici/index.ts b/packages/node/src/integrations/undici/index.ts index 222c75f852d8..5c27841c462d 100644 --- a/packages/node/src/integrations/undici/index.ts +++ b/packages/node/src/integrations/undici/index.ts @@ -1,5 +1,6 @@ import type { SentrySpan } from '@sentry/core'; import { + SPAN_STATUS_ERROR, addBreadcrumb, defineIntegration, getActiveSpan, @@ -278,7 +279,7 @@ export class Undici implements Integration { const span = request.__sentry_span__; if (span) { - span.setStatus('internal_error'); + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); span.end(); } diff --git a/packages/opentelemetry-node/src/utils/mapOtelStatus.ts b/packages/opentelemetry-node/src/utils/mapOtelStatus.ts index f7fd6e68bb7c..5a75db2a9eea 100644 --- a/packages/opentelemetry-node/src/utils/mapOtelStatus.ts +++ b/packages/opentelemetry-node/src/utils/mapOtelStatus.ts @@ -1,10 +1,11 @@ import { SpanStatusCode } from '@opentelemetry/api'; import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import type { SpanStatusType as SentryStatus } from '@sentry/core'; +import { SPAN_STATUS_ERROR, SPAN_STATUS_OK } from '@sentry/core'; +import type { SpanStatus } from '@sentry/types'; // canonicalCodesHTTPMap maps some HTTP codes to Sentry's span statuses. See possible mapping in https://develop.sentry.dev/sdk/event-payloads/span/ -const canonicalCodesHTTPMap: Record = { +const canonicalCodesHTTPMap: Record = { '400': 'failed_precondition', '401': 'unauthenticated', '403': 'permission_denied', @@ -19,7 +20,7 @@ const canonicalCodesHTTPMap: Record = { } as const; // canonicalCodesGrpcMap maps some GRPC codes to Sentry's span statuses. See description in grpc documentation. -const canonicalCodesGrpcMap: Record = { +const canonicalCodesGrpcMap: Record = { '1': 'cancelled', '2': 'unknown_error', '3': 'invalid_argument', @@ -44,7 +45,7 @@ const canonicalCodesGrpcMap: Record = { * @param otelSpan An otel span to generate a sentry status for. * @returns The Sentry span status */ -export function mapOtelStatus(otelSpan: OtelSpan): SentryStatus { +export function mapOtelStatus(otelSpan: OtelSpan): SpanStatus { const { status, attributes } = otelSpan; const httpCode = attributes[SemanticAttributes.HTTP_STATUS_CODE]; @@ -54,21 +55,21 @@ export function mapOtelStatus(otelSpan: OtelSpan): SentryStatus { if (code) { const sentryStatus = canonicalCodesHTTPMap[code]; if (sentryStatus) { - return sentryStatus; + return { code: SPAN_STATUS_ERROR, message: sentryStatus }; } } if (typeof grpcCode === 'string') { const sentryStatus = canonicalCodesGrpcMap[grpcCode]; if (sentryStatus) { - return sentryStatus; + return { code: SPAN_STATUS_ERROR, message: sentryStatus }; } } - const statusCode = status.code; + const statusCode = status && status.code; if (statusCode === SpanStatusCode.OK || statusCode === SpanStatusCode.UNSET) { - return 'ok'; + return { code: SPAN_STATUS_OK }; } - return 'unknown_error'; + return { code: SPAN_STATUS_ERROR, message: 'unknown_error' }; } diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts index d874a26fa1f0..c96c3032a822 100644 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ b/packages/opentelemetry-node/test/spanprocessor.test.ts @@ -4,11 +4,11 @@ import { Resource } from '@opentelemetry/resources'; import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; import { SemanticAttributes, SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import type { SpanStatusType } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; import { captureException, getCurrentScope, setCurrentClient } from '@sentry/core'; import { SentrySpan, Transaction, addTracingExtensions, createTransport, spanToJSON } from '@sentry/core'; import { NodeClient } from '@sentry/node-experimental'; +import type { SpanStatus } from '@sentry/types'; import { resolvedSyncPromise } from '@sentry/utils'; import { SentrySpanProcessor } from '../src/spanprocessor'; @@ -386,7 +386,7 @@ describe('SentrySpanProcessor', () => { }); }); - const statusTestTable: [number, undefined | number | string, undefined | string, SpanStatusType][] = [ + const statusTestTable: [number, undefined | number | string, undefined | string, SpanStatus['message']][] = [ [-1, undefined, undefined, 'unknown_error'], [3, undefined, undefined, 'unknown_error'], [0, undefined, undefined, 'ok'], diff --git a/packages/opentelemetry/src/utils/mapStatus.ts b/packages/opentelemetry/src/utils/mapStatus.ts index 065a626d1c38..ccde1c7ce178 100644 --- a/packages/opentelemetry/src/utils/mapStatus.ts +++ b/packages/opentelemetry/src/utils/mapStatus.ts @@ -1,12 +1,13 @@ import { SpanStatusCode } from '@opentelemetry/api'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import type { SpanStatusType as SentryStatus } from '@sentry/core'; +import { SPAN_STATUS_ERROR, SPAN_STATUS_OK } from '@sentry/core'; +import type { SpanStatus } from '@sentry/types'; import type { AbstractSpan } from '../types'; import { spanHasAttributes, spanHasStatus } from './spanTypes'; // canonicalCodesHTTPMap maps some HTTP codes to Sentry's span statuses. See possible mapping in https://develop.sentry.dev/sdk/event-payloads/span/ -const canonicalCodesHTTPMap: Record = { +const canonicalCodesHTTPMap: Record = { '400': 'failed_precondition', '401': 'unauthenticated', '403': 'permission_denied', @@ -21,7 +22,7 @@ const canonicalCodesHTTPMap: Record = { } as const; // canonicalCodesGrpcMap maps some GRPC codes to Sentry's span statuses. See description in grpc documentation. -const canonicalCodesGrpcMap: Record = { +const canonicalCodesGrpcMap: Record = { '1': 'cancelled', '2': 'unknown_error', '3': 'invalid_argument', @@ -43,7 +44,7 @@ const canonicalCodesGrpcMap: Record = { /** * Get a Sentry span status from an otel span. */ -export function mapStatus(span: AbstractSpan): SentryStatus { +export function mapStatus(span: AbstractSpan): SpanStatus { const attributes = spanHasAttributes(span) ? span.attributes : {}; const status = spanHasStatus(span) ? span.status : undefined; @@ -54,21 +55,21 @@ export function mapStatus(span: AbstractSpan): SentryStatus { if (code) { const sentryStatus = canonicalCodesHTTPMap[code]; if (sentryStatus) { - return sentryStatus; + return { code: SPAN_STATUS_ERROR, message: sentryStatus }; } } if (typeof grpcCode === 'string') { const sentryStatus = canonicalCodesGrpcMap[grpcCode]; if (sentryStatus) { - return sentryStatus; + return { code: SPAN_STATUS_ERROR, message: sentryStatus }; } } const statusCode = status && status.code; if (statusCode === SpanStatusCode.OK || statusCode === SpanStatusCode.UNSET) { - return 'ok'; + return { code: SPAN_STATUS_OK }; } - return 'unknown_error'; + return { code: SPAN_STATUS_ERROR, message: 'unknown_error' }; } diff --git a/packages/opentelemetry/test/utils/mapStatus.test.ts b/packages/opentelemetry/test/utils/mapStatus.test.ts index 4fa6cc664b61..eb9182973d23 100644 --- a/packages/opentelemetry/test/utils/mapStatus.test.ts +++ b/packages/opentelemetry/test/utils/mapStatus.test.ts @@ -1,65 +1,66 @@ import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import type { SpanStatusType } from '@sentry/core'; +import { SPAN_STATUS_ERROR, SPAN_STATUS_OK } from '@sentry/core'; +import type { SpanStatus } from '@sentry/types'; import { mapStatus } from '../../src/utils/mapStatus'; import { createSpan } from '../helpers/createSpan'; describe('mapStatus', () => { - const statusTestTable: [number, undefined | number | string, undefined | string, SpanStatusType][] = [ - [-1, undefined, undefined, 'unknown_error'], - [3, undefined, undefined, 'unknown_error'], - [0, undefined, undefined, 'ok'], - [1, undefined, undefined, 'ok'], - [2, undefined, undefined, 'unknown_error'], + const statusTestTable: [number, undefined | number | string, undefined | string, SpanStatus][] = [ + [-1, undefined, undefined, { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], + [3, undefined, undefined, { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], + [0, undefined, undefined, { code: SPAN_STATUS_OK }], + [1, undefined, undefined, { code: SPAN_STATUS_OK }], + [2, undefined, undefined, { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], // http codes - [2, 400, undefined, 'failed_precondition'], - [2, 401, undefined, 'unauthenticated'], - [2, 403, undefined, 'permission_denied'], - [2, 404, undefined, 'not_found'], - [2, 409, undefined, 'aborted'], - [2, 429, undefined, 'resource_exhausted'], - [2, 499, undefined, 'cancelled'], - [2, 500, undefined, 'internal_error'], - [2, 501, undefined, 'unimplemented'], - [2, 503, undefined, 'unavailable'], - [2, 504, undefined, 'deadline_exceeded'], - [2, 999, undefined, 'unknown_error'], + [2, 400, undefined, { code: SPAN_STATUS_ERROR, message: 'failed_precondition' }], + [2, 401, undefined, { code: SPAN_STATUS_ERROR, message: 'unauthenticated' }], + [2, 403, undefined, { code: SPAN_STATUS_ERROR, message: 'permission_denied' }], + [2, 404, undefined, { code: SPAN_STATUS_ERROR, message: 'not_found' }], + [2, 409, undefined, { code: SPAN_STATUS_ERROR, message: 'aborted' }], + [2, 429, undefined, { code: SPAN_STATUS_ERROR, message: 'resource_exhausted' }], + [2, 499, undefined, { code: SPAN_STATUS_ERROR, message: 'cancelled' }], + [2, 500, undefined, { code: SPAN_STATUS_ERROR, message: 'internal_error' }], + [2, 501, undefined, { code: SPAN_STATUS_ERROR, message: 'unimplemented' }], + [2, 503, undefined, { code: SPAN_STATUS_ERROR, message: 'unavailable' }], + [2, 504, undefined, { code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }], + [2, 999, undefined, { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], - [2, '400', undefined, 'failed_precondition'], - [2, '401', undefined, 'unauthenticated'], - [2, '403', undefined, 'permission_denied'], - [2, '404', undefined, 'not_found'], - [2, '409', undefined, 'aborted'], - [2, '429', undefined, 'resource_exhausted'], - [2, '499', undefined, 'cancelled'], - [2, '500', undefined, 'internal_error'], - [2, '501', undefined, 'unimplemented'], - [2, '503', undefined, 'unavailable'], - [2, '504', undefined, 'deadline_exceeded'], - [2, '999', undefined, 'unknown_error'], + [2, '400', undefined, { code: SPAN_STATUS_ERROR, message: 'failed_precondition' }], + [2, '401', undefined, { code: SPAN_STATUS_ERROR, message: 'unauthenticated' }], + [2, '403', undefined, { code: SPAN_STATUS_ERROR, message: 'permission_denied' }], + [2, '404', undefined, { code: SPAN_STATUS_ERROR, message: 'not_found' }], + [2, '409', undefined, { code: SPAN_STATUS_ERROR, message: 'aborted' }], + [2, '429', undefined, { code: SPAN_STATUS_ERROR, message: 'resource_exhausted' }], + [2, '499', undefined, { code: SPAN_STATUS_ERROR, message: 'cancelled' }], + [2, '500', undefined, { code: SPAN_STATUS_ERROR, message: 'internal_error' }], + [2, '501', undefined, { code: SPAN_STATUS_ERROR, message: 'unimplemented' }], + [2, '503', undefined, { code: SPAN_STATUS_ERROR, message: 'unavailable' }], + [2, '504', undefined, { code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }], + [2, '999', undefined, { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], // grpc codes - [2, undefined, '1', 'cancelled'], - [2, undefined, '2', 'unknown_error'], - [2, undefined, '3', 'invalid_argument'], - [2, undefined, '4', 'deadline_exceeded'], - [2, undefined, '5', 'not_found'], - [2, undefined, '6', 'already_exists'], - [2, undefined, '7', 'permission_denied'], - [2, undefined, '8', 'resource_exhausted'], - [2, undefined, '9', 'failed_precondition'], - [2, undefined, '10', 'aborted'], - [2, undefined, '11', 'out_of_range'], - [2, undefined, '12', 'unimplemented'], - [2, undefined, '13', 'internal_error'], - [2, undefined, '14', 'unavailable'], - [2, undefined, '15', 'data_loss'], - [2, undefined, '16', 'unauthenticated'], - [2, undefined, '999', 'unknown_error'], + [2, undefined, '1', { code: SPAN_STATUS_ERROR, message: 'cancelled' }], + [2, undefined, '2', { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], + [2, undefined, '3', { code: SPAN_STATUS_ERROR, message: 'invalid_argument' }], + [2, undefined, '4', { code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }], + [2, undefined, '5', { code: SPAN_STATUS_ERROR, message: 'not_found' }], + [2, undefined, '6', { code: SPAN_STATUS_ERROR, message: 'already_exists' }], + [2, undefined, '7', { code: SPAN_STATUS_ERROR, message: 'permission_denied' }], + [2, undefined, '8', { code: SPAN_STATUS_ERROR, message: 'resource_exhausted' }], + [2, undefined, '9', { code: SPAN_STATUS_ERROR, message: 'failed_precondition' }], + [2, undefined, '10', { code: SPAN_STATUS_ERROR, message: 'aborted' }], + [2, undefined, '11', { code: SPAN_STATUS_ERROR, message: 'out_of_range' }], + [2, undefined, '12', { code: SPAN_STATUS_ERROR, message: 'unimplemented' }], + [2, undefined, '13', { code: SPAN_STATUS_ERROR, message: 'internal_error' }], + [2, undefined, '14', { code: SPAN_STATUS_ERROR, message: 'unavailable' }], + [2, undefined, '15', { code: SPAN_STATUS_ERROR, message: 'data_loss' }], + [2, undefined, '16', { code: SPAN_STATUS_ERROR, message: 'unauthenticated' }], + [2, undefined, '999', { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], // http takes precedence over grpc - [2, '400', '2', 'failed_precondition'], + [2, '400', '2', { code: SPAN_STATUS_ERROR, message: 'failed_precondition' }], ]; it.each(statusTestTable)( diff --git a/packages/tracing-internal/src/browser/backgroundtab.ts b/packages/tracing-internal/src/browser/backgroundtab.ts index a221a7c09c82..ad0c57d55fe6 100644 --- a/packages/tracing-internal/src/browser/backgroundtab.ts +++ b/packages/tracing-internal/src/browser/backgroundtab.ts @@ -1,4 +1,4 @@ -import { getActiveSpan, getRootSpan } from '@sentry/core'; +import { SPAN_STATUS_ERROR, getActiveSpan, getRootSpan } from '@sentry/core'; import { spanToJSON } from '@sentry/core'; import { logger } from '@sentry/utils'; @@ -34,7 +34,7 @@ export function registerBackgroundTabDetection(): void { // We should not set status if it is already set, this prevent important statuses like // error or data loss from being overwritten on transaction. if (!status) { - rootSpan.setStatus(cancelledStatus); + rootSpan.setStatus({ code: SPAN_STATUS_ERROR, message: cancelledStatus }); } rootSpan.setAttribute('sentry.cancellation_reason', 'document.hidden'); diff --git a/packages/tracing-internal/src/common/fetch.ts b/packages/tracing-internal/src/common/fetch.ts index 6087a65a2593..aa50154abc5a 100644 --- a/packages/tracing-internal/src/common/fetch.ts +++ b/packages/tracing-internal/src/common/fetch.ts @@ -1,5 +1,6 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SPAN_STATUS_ERROR, getClient, getCurrentScope, getDynamicSamplingContextFromClient, @@ -66,7 +67,7 @@ export function instrumentFetchRequest( } } } else if (handlerData.error) { - span.setStatus('internal_error'); + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); } span.end(); diff --git a/packages/tracing-internal/src/exports/index.ts b/packages/tracing-internal/src/exports/index.ts index 132a4417570e..284f16edbf9d 100644 --- a/packages/tracing-internal/src/exports/index.ts +++ b/packages/tracing-internal/src/exports/index.ts @@ -3,10 +3,7 @@ export { getActiveTransaction, hasTracingEnabled, IdleTransaction, - // eslint-disable-next-line deprecation/deprecation - SpanStatus, startIdleTransaction, Transaction, } from '@sentry/core'; -export type { SpanStatusType } from '@sentry/core'; export { stripUrlQueryAndFragment, TRACEPARENT_REGEXP } from '@sentry/utils'; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 57c565a86d6e..0034935e7201 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -100,6 +100,7 @@ export type { TraceFlag, MetricSummary, } from './span'; +export type { SpanStatus } from './spanStatus'; export type { StackFrame } from './stackframe'; export type { Stacktrace, StackParser, StackLineParser, StackLineParserFn } from './stacktrace'; export type { PropagationContext, TracePropagationTargets } from './tracing'; diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index 018e84e977ec..ddc3057e6cbf 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -1,5 +1,6 @@ import type { Primitive } from './misc'; import type { HrTime } from './opentelemetry'; +import type { SpanStatus } from './spanStatus'; import type { TransactionSource } from './transaction'; type SpanOriginType = 'manual' | 'auto'; @@ -192,11 +193,9 @@ export interface Span { setAttributes(attributes: SpanAttributes): void; /** - * Sets the status attribute on the current span - * See: {@sentry/core SpanStatusType} for possible values - * @param status http code used to set the status + * Sets the status attribute on the current span. */ - setStatus(status: string): this; + setStatus(status: SpanStatus): this; /** * Update the name of the span. diff --git a/packages/types/src/spanStatus.ts b/packages/types/src/spanStatus.ts new file mode 100644 index 000000000000..8347d37adb1a --- /dev/null +++ b/packages/types/src/spanStatus.ts @@ -0,0 +1,60 @@ +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'; + +// These are aligned with OpenTelemetry span status codes +const SPAN_STATUS_UNSET = 0; +const SPAN_STATUS_OK = 1; +const SPAN_STATUS_ERROR = 2; +/** The status code of a span. */ +export type SpanStatusCode = typeof SPAN_STATUS_UNSET | typeof SPAN_STATUS_OK | typeof SPAN_STATUS_ERROR; + +/** + * The status of a span. + * This can optionally contain a human-readable message. + */ +export interface SpanStatus { + /** + * The status code of this message. + * 0 = UNSET + * 1 = OK + * 2 = ERROR + */ + code: SpanStatusCode; + /** + * A developer-facing error message. + */ + message?: SpanStatusType | string; +} diff --git a/packages/types/src/startSpanOptions.ts b/packages/types/src/startSpanOptions.ts index 01293e129c74..d884d7edaeff 100644 --- a/packages/types/src/startSpanOptions.ts +++ b/packages/types/src/startSpanOptions.ts @@ -53,11 +53,6 @@ export interface StartSpanOptions extends TransactionContext { */ metadata?: Partial; - /** - * @deprecated Use `span.setStatus()` instead. - */ - status?: string; - /** * @deprecated Use `scope` instead. */ diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index f09c61bdac90..3ea421c7c0f3 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -78,7 +78,6 @@ export { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, } from '@sentry/core'; -export type { SpanStatusType } from '@sentry/core'; export { VercelEdgeClient } from './client'; export {