diff --git a/packages/core/src/shared-exports.ts b/packages/core/src/shared-exports.ts index 3f8e74364c5b..97cc2cb40b7d 100644 --- a/packages/core/src/shared-exports.ts +++ b/packages/core/src/shared-exports.ts @@ -130,7 +130,7 @@ export { } from './utils/request'; export type { MaxRequestBodySize } from './utils/request'; export { DEFAULT_ENVIRONMENT, DEV_ENVIRONMENT } from './constants'; -export { SPAN_KIND } from './spanKind'; +export { SPAN_KIND, spanKindToName } from './spanKind'; export type { SpanKindValue } from './spanKind'; export { addBreadcrumb } from './breadcrumbs'; export { functionToStringIntegration } from './integrations/functiontostring'; diff --git a/packages/core/src/spanKind.ts b/packages/core/src/spanKind.ts index 3127d9205a9b..5a08ebadf572 100644 --- a/packages/core/src/spanKind.ts +++ b/packages/core/src/spanKind.ts @@ -15,3 +15,22 @@ export const SPAN_KIND = { } as const; export type SpanKindValue = (typeof SPAN_KIND)[keyof typeof SPAN_KIND]; + +// Reverse of SPAN_KIND (value → name), for the `otel.kind` attribute. The numeric keys come from +// SPAN_KIND so they stay in sync; `satisfies` ensures every kind has a name. +const SPAN_KIND_NAME = { + [SPAN_KIND.INTERNAL]: 'INTERNAL', + [SPAN_KIND.SERVER]: 'SERVER', + [SPAN_KIND.CLIENT]: 'CLIENT', + [SPAN_KIND.PRODUCER]: 'PRODUCER', + [SPAN_KIND.CONSUMER]: 'CONSUMER', +} as const satisfies Record; + +/** + * Resolve the string name of a span kind value (e.g. `1` → `'SERVER'`), mirroring the reverse + * mapping of OpenTelemetry's `SpanKind` enum. Used for the `otel.kind` span attribute, so SDK + * code doesn't need to import `@opentelemetry/api` just for that reverse lookup. + */ +export function spanKindToName(kind: number): (typeof SPAN_KIND_NAME)[SpanKindValue] | undefined { + return SPAN_KIND_NAME[kind as SpanKindValue]; +} diff --git a/packages/core/test/lib/spanKind.test.ts b/packages/core/test/lib/spanKind.test.ts new file mode 100644 index 000000000000..918a31488c62 --- /dev/null +++ b/packages/core/test/lib/spanKind.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest'; +import { SPAN_KIND, spanKindToName } from '../../src/spanKind'; + +describe('spanKindToName', () => { + it('resolves each span kind value to its name', () => { + expect(spanKindToName(SPAN_KIND.INTERNAL)).toBe('INTERNAL'); + expect(spanKindToName(SPAN_KIND.SERVER)).toBe('SERVER'); + expect(spanKindToName(SPAN_KIND.CLIENT)).toBe('CLIENT'); + expect(spanKindToName(SPAN_KIND.PRODUCER)).toBe('PRODUCER'); + expect(spanKindToName(SPAN_KIND.CONSUMER)).toBe('CONSUMER'); + }); + + it('returns undefined for an unknown kind value', () => { + expect(spanKindToName(99)).toBeUndefined(); + }); +}); diff --git a/packages/opentelemetry/src/spanProcessor.ts b/packages/opentelemetry/src/spanProcessor.ts index 712b6ff43e0f..fb33d0daf4c5 100644 --- a/packages/opentelemetry/src/spanProcessor.ts +++ b/packages/opentelemetry/src/spanProcessor.ts @@ -1,5 +1,5 @@ import type { Context } from '@opentelemetry/api'; -import { ROOT_CONTEXT, SpanKind, trace } from '@opentelemetry/api'; +import { ROOT_CONTEXT, trace } from '@opentelemetry/api'; import type { ReadableSpan, Span, SpanProcessor as SpanProcessorInterface } from '@opentelemetry/sdk-trace-base'; import type { Client, SpanAttributes, StreamedSpanJSON } from '@sentry/core'; import { @@ -14,6 +14,8 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, setCapturedScopesOnSpan, + SPAN_KIND, + spanKindToName, } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_PARENT_IS_REMOTE } from './semanticAttributes'; import { SentrySpanExporter } from './spanExporter'; @@ -121,7 +123,7 @@ function backfillStreamedSpanDataFromOtel(spanJSON: StreamedSpanJSON, hint?: { s return; } - const kind = hint?.spanKind ?? SpanKind.INTERNAL; + const kind = hint?.spanKind ?? SPAN_KIND.INTERNAL; const { op, description, source, data } = inferSpanData(spanJSON.name, attributes as unknown as SpanAttributes, kind); spanJSON.name = description; @@ -132,9 +134,9 @@ function backfillStreamedSpanDataFromOtel(spanJSON: StreamedSpanJSON, hint?: { s ...data, }); - if (kind !== SpanKind.INTERNAL) { + if (kind !== SPAN_KIND.INTERNAL) { safeSetSpanJSONAttributes(spanJSON, { - 'otel.kind': SpanKind[kind], + 'otel.kind': spanKindToName(kind), }); } }