From f9fd8fc64ebde924c62adf29d7d381e60fe11a69 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Mon, 31 Aug 2026 10:50:13 +0200 Subject: [PATCH 1/4] feat(core): Deprecate `forceTransaction` span start option `forceTransaction` no longer has a concrete use case: all spans are indexed and searchable in Sentry, so a span does not need to be a transaction to be queried, filtered or aggregated on. The option will be removed in the next major version. For the remaining cases where a span genuinely has to be a segment (root) span, the JSDoc points to starting it without a parent span (`withActiveSpan(null, ...)`, optionally inside `continueTrace`) instead of forcing it into a transaction. Internal SDK usages are suppressed with an oxlint directive for now and will be evaluated separately. Co-Authored-By: Claude Opus 5 (1M context) --- .../src/core/build-plugin-manager.ts | 2 ++ .../core/src/integrations/mcp-server/spans.ts | 1 + packages/core/src/tracing/trace.ts | 3 +++ packages/core/src/trpc.ts | 1 + packages/core/src/types/startSpanOptions.ts | 25 +++++++++++++++++++ packages/nestjs/src/integrations/helpers.ts | 1 + .../common/withServerActionInstrumentation.ts | 1 + 7 files changed, 34 insertions(+) diff --git a/packages/bundler-plugins/src/core/build-plugin-manager.ts b/packages/bundler-plugins/src/core/build-plugin-manager.ts index b7f5082063a3..242a77d73f0d 100644 --- a/packages/bundler-plugins/src/core/build-plugin-manager.ts +++ b/packages/bundler-plugins/src/core/build-plugin-manager.ts @@ -524,6 +524,7 @@ export function createSentryBuildPluginManager( Only use this if you need to manually inject debug IDs into the build artifacts. */ async injectDebugIds(buildArtifactPaths: string[]) { + // oxlint-disable-next-line typescript/no-deprecated await startSpan({ name: 'inject-debug-ids', scope: sentryScope, forceTransaction: true }, async () => { try { const cliInstance = new SentryCliAdapter(options); @@ -561,6 +562,7 @@ export function createSentryBuildPluginManager( await startSpan( // This is `forceTransaction`ed because this span is used in dashboards in the form of indexed transactions. + // oxlint-disable-next-line typescript/no-deprecated { name: 'debug-id-sourcemap-upload', scope: sentryScope, forceTransaction: true }, async () => { // If we're not using a temp folder, we must not prepare artifacts in-place (to avoid mutating user files) diff --git a/packages/core/src/integrations/mcp-server/spans.ts b/packages/core/src/integrations/mcp-server/spans.ts index 76b3ba4ffa3e..7277d199a06e 100644 --- a/packages/core/src/integrations/mcp-server/spans.ts +++ b/packages/core/src/integrations/mcp-server/spans.ts @@ -115,6 +115,7 @@ function createMcpSpan(config: McpSpanConfig): unknown { return startSpan( { name: spanName, + // oxlint-disable-next-line typescript/no-deprecated forceTransaction: true, attributes, }, diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index b52d62624f23..b7ac40595830 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -52,6 +52,7 @@ import { SUPPRESS_TRACING_KEY } from './constants'; */ export function startSpan(options: StartSpanOptions, callback: (span: Span) => T): T { const spanArguments = parseSentrySpanArguments(options); + // oxlint-disable-next-line typescript/no-deprecated const { forceTransaction, parentSpan: customParentSpan, scope: customScope } = options; // We still need to fork a potentially passed scope, as we set the active span on it @@ -104,6 +105,7 @@ export function startSpan(options: StartSpanOptions, callback: (span: Span) = */ export function startSpanManual(options: StartSpanOptions, callback: (span: Span, finish: () => void) => T): T { const spanArguments = parseSentrySpanArguments(options); + // oxlint-disable-next-line typescript/no-deprecated const { forceTransaction, parentSpan: customParentSpan, scope: customScope } = options; const customForkedScope = customScope?.clone(); @@ -150,6 +152,7 @@ export function startSpanManual(options: StartSpanOptions, callback: (span: S */ export function startInactiveSpan(options: StartSpanOptions): Span { const spanArguments = parseSentrySpanArguments(options); + // oxlint-disable-next-line typescript/no-deprecated const { forceTransaction, parentSpan: customParentSpan, scope: customScope } = options; // If `options.scope` is defined, we use this as as a wrapper, diff --git a/packages/core/src/trpc.ts b/packages/core/src/trpc.ts index e08248b3bbe4..55e428f60458 100644 --- a/packages/core/src/trpc.ts +++ b/packages/core/src/trpc.ts @@ -103,6 +103,7 @@ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { [TRPC_PROCEDURE_PATH]: String(path), [TRPC_PROCEDURE_TYPE]: String(type), }, + // oxlint-disable-next-line typescript/no-deprecated forceTransaction: !!options.forceTransaction, }, async span => { diff --git a/packages/core/src/types/startSpanOptions.ts b/packages/core/src/types/startSpanOptions.ts index 9499768836b3..ad0589090853 100644 --- a/packages/core/src/types/startSpanOptions.ts +++ b/packages/core/src/types/startSpanOptions.ts @@ -39,6 +39,31 @@ export interface StartSpanOptions { * If set to true, this span will be forced to be treated as a transaction in the Sentry UI, if possible and applicable. * Note that it is up to the SDK to decide how exactly the span will be sent, which may change in future SDK versions. * It is not guaranteed that a span started with this flag set to `true` will be sent as a transaction. + * + * @deprecated This option will be removed in the next major version of the SDK. There is no longer a concrete use + * case for it: all spans are indexed and searchable in Sentry, so a span no longer needs to be a transaction to be + * queried, filtered or aggregated on. In most cases, simply drop the option - the span is still sent, just as a child + * of its parent span. If you do need the span to be a segment (root) span, start it without a parent span instead. + * + * @example Making a span a segment span without forcing it into a transaction + * ```js + * Sentry.withActiveSpan(null, () => { + * Sentry.startSpan({ name: 'span-that-should-be-a-segment' }, () => { + * // ... + * }); + * }); + * ``` + * + * @example Keeping that segment span attached to an incoming trace + * ```js + * Sentry.continueTrace({ sentryTrace, baggage }, () => + * Sentry.withActiveSpan(null, () => + * Sentry.startSpan({ name: 'span-that-should-be-a-segment' }, () => { + * // ... + * }), + * ), + * ); + * ``` */ forceTransaction?: boolean; diff --git a/packages/nestjs/src/integrations/helpers.ts b/packages/nestjs/src/integrations/helpers.ts index 655f53c6b53b..cfbdc1b2dd61 100644 --- a/packages/nestjs/src/integrations/helpers.ts +++ b/packages/nestjs/src/integrations/helpers.ts @@ -117,6 +117,7 @@ export function getEventSpanOptions(event: string): { [SENTRY_OP]: FUNCTION, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.event.nestjs', }, + // oxlint-disable-next-line typescript/no-deprecated forceTransaction: true, }; } diff --git a/packages/nextjs/src/common/withServerActionInstrumentation.ts b/packages/nextjs/src/common/withServerActionInstrumentation.ts index aa02017e38c4..fb32aaf6ca90 100644 --- a/packages/nextjs/src/common/withServerActionInstrumentation.ts +++ b/packages/nextjs/src/common/withServerActionInstrumentation.ts @@ -118,6 +118,7 @@ async function withServerActionInstrumentationImplementation Date: Mon, 31 Aug 2026 11:11:11 +0200 Subject: [PATCH 2/4] feat(core): Deprecate `op` span start option `op` duplicates the `sentry.op` span attribute, which is the canonical way to categorize a span. It will be removed in a future version; the deprecation deliberately does not name a specific one. All internal usages are rewritten to set `sentry.op` (via the `SENTRY_OP` constant from `@sentry/conventions`) instead. Precedence is unchanged everywhere: an explicit `sentry.op` attribute still wins over `op`. Call sites that already set both now only set the attribute. `browserTracingIntegration` is the one exception that still touches `op`: `beforeStartSpan` is a public hook that receives and may override it, so the option is kept in the options handed to the hook and only folded into `sentry.op` afterwards. Co-Authored-By: Claude Opus 5 (1M context) --- packages/astro/src/server/middleware.ts | 5 ++- .../browser-utils/src/performance/entries.ts | 11 ++++--- .../src/performance/userTiming.ts | 2 +- .../src/tracing/browserTracingIntegration.ts | 32 ++++++++++++++----- .../instrumentDurableObjectSyncKvStorage.ts | 2 +- .../instrumentations/instrumentSqlStorage.ts | 2 +- .../instrumentations/worker/instrumentR2.ts | 1 - .../worker/instrumentR2.test.ts | 7 +--- .../core/src/integrations/mcp-server/spans.ts | 1 + packages/core/src/tracing/trace.ts | 2 ++ packages/core/src/types/startSpanOptions.ts | 14 +++++++- packages/nestjs/src/decorators.ts | 1 - packages/nestjs/test/decorators.test.ts | 3 -- .../server-utils/src/ai/anthropic-ai/index.ts | 9 ++++-- .../server-utils/src/ai/google-genai/index.ts | 13 +++++--- packages/server-utils/src/ai/openai/index.ts | 7 ++-- .../src/integrations/anthropic.ts | 8 +++-- .../src/integrations/aws-sdk/index.ts | 7 ++-- .../src/integrations/google-genai.ts | 6 ++-- .../server-utils/src/integrations/knex.ts | 5 ++- .../server-utils/src/integrations/openai.ts | 7 ++-- .../lib/tracing/langchain-embeddings.test.ts | 1 + .../client/browserTracingIntegration.test.ts | 6 ++-- .../test/client/svelte5BrowserTracing.test.ts | 6 ++-- packages/vue/src/router.ts | 1 + 25 files changed, 105 insertions(+), 54 deletions(-) diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index d22d0c35be88..7c2f744480f9 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -252,7 +252,10 @@ async function instrumentRequestStartHttpServerSpan( const res = await startSpan( { - attributes, + attributes: { + [SENTRY_OP]: 'http.server', + ...attributes, + }, name, }, async span => { diff --git a/packages/browser-utils/src/performance/entries.ts b/packages/browser-utils/src/performance/entries.ts index 60ced66f55c2..0223bf2c5708 100644 --- a/packages/browser-utils/src/performance/entries.ts +++ b/packages/browser-utils/src/performance/entries.ts @@ -118,8 +118,8 @@ export function startTrackingLongTasks(): void { startAndEndSpan(parent, startTime, startTime + duration, { name: 'Main UI thread blocked', - op: UI_LONG_TASK, attributes: { + [SENTRY_OP]: UI_LONG_TASK, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', }, }); @@ -161,6 +161,7 @@ export function startTrackingLongAnimationFrames(): void { const duration = msToSec(entry.duration); const attributes: SpanAttributes = { + [SENTRY_OP]: UI_LONG_ANIMATION_FRAME, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', }; @@ -180,7 +181,6 @@ export function startTrackingLongAnimationFrames(): void { startAndEndSpan(parent, startTime, startTime + duration, { name: 'Main UI thread blocked', - op: UI_LONG_ANIMATION_FRAME, attributes, }); } @@ -464,7 +464,11 @@ export function _addResourceSpans( ['deliveryType', 'http.response_delivery_type'], ]); - const attributesWithResourceTiming: SpanAttributes = { ...attributes, ...resourceTimingToSpanAttributes(entry) }; + const attributesWithResourceTiming: SpanAttributes = { + [SENTRY_OP]: op, + ...attributes, + ...resourceTimingToSpanAttributes(entry), + }; const startTimestamp = timeOrigin + startTime; const endTimestamp = startTimestamp + duration; @@ -474,7 +478,6 @@ export function _addResourceSpans( name: spanStreamingEnabled ? domain || RESOURCE_SPAN_NAME_FALLBACK : resourceUrl.replace(WINDOW.location.origin, ''), - op, attributes: attributesWithResourceTiming, }); } diff --git a/packages/browser-utils/src/performance/userTiming.ts b/packages/browser-utils/src/performance/userTiming.ts index 02d6fc25694d..7ada7f4cdd52 100644 --- a/packages/browser-utils/src/performance/userTiming.ts +++ b/packages/browser-utils/src/performance/userTiming.ts @@ -116,6 +116,7 @@ export function _addUserTimingSpan( const spanEndTimestamp = originalStartTimestamp + duration; const attributes: SpanAttributes = { + [SENTRY_OP]: entry.entryType, [SENTRY_ORIGIN]: `auto.browser.user_timing.${entry.entryType}`, }; @@ -130,7 +131,6 @@ export function _addUserTimingSpan( if (spanStartTimestamp <= spanEndTimestamp) { startAndEndSpan(parentSpan, spanStartTimestamp, spanEndTimestamp, { name: entry.name, - op: entry.entryType, attributes, }); } diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts index 49e6abf319c5..54b442c63e62 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -5,6 +5,7 @@ import type { RequestHookInfo, ResponseHookInfo, Span, + SpanAttributes, StartSpanOptions, } from '@sentry/core/browser'; import { @@ -330,24 +331,38 @@ export const browserTracingIntegration = ((options: Partial { expect(startSpanSpy).toHaveBeenCalledTimes(1); expect(startSpanSpy).toHaveBeenLastCalledWith( expect.objectContaining({ - op: 'object.get', name: 'r2_get', attributes: expect.objectContaining({ 'cloudflare.r2.operation': 'GetObject', @@ -112,7 +111,6 @@ describe('instrumentR2Bucket', () => { expect(startSpanSpy).toHaveBeenCalledTimes(1); expect(startSpanSpy).toHaveBeenLastCalledWith( expect.objectContaining({ - op: 'object.head', name: 'r2_head', attributes: expect.objectContaining({ 'cloudflare.r2.operation': 'HeadObject', @@ -142,7 +140,6 @@ describe('instrumentR2Bucket', () => { expect(startSpanSpy).toHaveBeenCalledTimes(1); expect(startSpanSpy).toHaveBeenLastCalledWith( expect.objectContaining({ - op: 'object.put', name: 'r2_put', attributes: expect.objectContaining({ 'cloudflare.r2.operation': 'PutObject', @@ -170,7 +167,6 @@ describe('instrumentR2Bucket', () => { expect(startSpanSpy).toHaveBeenCalledTimes(1); expect(startSpanSpy).toHaveBeenLastCalledWith( expect.objectContaining({ - op: 'object.delete', name: 'r2_delete', attributes: expect.objectContaining({ 'cloudflare.r2.operation': 'DeleteObject', @@ -206,7 +202,6 @@ describe('instrumentR2Bucket', () => { expect(startSpanSpy).toHaveBeenCalledTimes(1); expect(startSpanSpy).toHaveBeenLastCalledWith( expect.objectContaining({ - op: 'object.list', name: 'r2_list', attributes: expect.objectContaining({ 'cloudflare.r2.operation': 'ListObjects', @@ -236,11 +231,11 @@ describe('instrumentR2Bucket', () => { expect(startSpanSpy).toHaveBeenCalledTimes(1); expect(startSpanSpy).toHaveBeenLastCalledWith( expect.objectContaining({ - op: 'object.multipart_upload.create', name: 'r2_createMultipartUpload', attributes: expect.objectContaining({ 'cloudflare.r2.operation': 'CreateMultipartUpload', 'cloudflare.r2.request.key': 'big-file.bin', + 'sentry.op': 'object.multipart_upload.create', }), }), expect.any(Function), diff --git a/packages/core/src/integrations/mcp-server/spans.ts b/packages/core/src/integrations/mcp-server/spans.ts index 7277d199a06e..48ec574091c8 100644 --- a/packages/core/src/integrations/mcp-server/spans.ts +++ b/packages/core/src/integrations/mcp-server/spans.ts @@ -212,6 +212,7 @@ export function buildMcpServerSpanConfig( return { name: spanName, + // oxlint-disable-next-line typescript/no-deprecated forceTransaction: true, attributes, }; diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index b7ac40595830..6e928802757f 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -445,8 +445,10 @@ function parseSentrySpanArguments(options: StartSpanOptions): SentrySpanArgument // Fold `op` into the attributes up front so samplers see `sentry.op`; the `SentrySpan` // constructor only adds it after the sampling decision. An explicit `sentry.op` attribute wins. + // oxlint-disable-next-line typescript/no-deprecated if (options.op) { initialCtx.attributes = { + // oxlint-disable-next-line typescript/no-deprecated [SEMANTIC_ATTRIBUTE_SENTRY_OP]: options.op, ...options.attributes, }; diff --git a/packages/core/src/types/startSpanOptions.ts b/packages/core/src/types/startSpanOptions.ts index ad0589090853..eb7d37a41c3b 100644 --- a/packages/core/src/types/startSpanOptions.ts +++ b/packages/core/src/types/startSpanOptions.ts @@ -25,7 +25,19 @@ export interface StartSpanOptions { /** If set to true, only start a span if a parent span exists. */ onlyIfParent?: boolean; - /** An op for the span. This is a categorization for spans. */ + /** + * An op for the span. This is a categorization for spans. + * + * @deprecated This option will be removed in a future version of the SDK. Set the `sentry.op` attribute instead. + * If both are set, the attribute takes precedence. + * + * @example + * ```js + * Sentry.startSpan({ name: 'my-span', attributes: { 'sentry.op': 'my.op' } }, () => { + * // ... + * }); + * ``` + */ op?: string; /** diff --git a/packages/nestjs/src/decorators.ts b/packages/nestjs/src/decorators.ts index f50c3c9bcc68..ec05a1dca1ce 100644 --- a/packages/nestjs/src/decorators.ts +++ b/packages/nestjs/src/decorators.ts @@ -38,7 +38,6 @@ export function SentryTraced(op: string = 'function') { descriptor.value = function (...args: unknown[]) { return startSpan( { - op: op, name: propertyKey, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nestjs.sentry_traced', diff --git a/packages/nestjs/test/decorators.test.ts b/packages/nestjs/test/decorators.test.ts index b5d17451a9f2..6a5556595f59 100644 --- a/packages/nestjs/test/decorators.test.ts +++ b/packages/nestjs/test/decorators.test.ts @@ -37,7 +37,6 @@ describe('SentryTraced decorator', () => { expect(startSpanSpy).toHaveBeenCalledTimes(1); expect(startSpanSpy).toHaveBeenCalledWith( { - op: 'test-operation', name: 'testMethod', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nestjs.sentry_traced', @@ -70,7 +69,6 @@ describe('SentryTraced decorator', () => { expect(startSpanSpy).toHaveBeenCalledTimes(1); expect(startSpanSpy).toHaveBeenCalledWith( { - op: 'function', // default value name: 'testDefaultOp', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nestjs.sentry_traced', @@ -103,7 +101,6 @@ describe('SentryTraced decorator', () => { expect(startSpanSpy).toHaveBeenCalledTimes(1); expect(startSpanSpy).toHaveBeenCalledWith( { - op: 'sync-operation', name: 'syncMethod', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nestjs.sentry_traced', diff --git a/packages/server-utils/src/ai/anthropic-ai/index.ts b/packages/server-utils/src/ai/anthropic-ai/index.ts index a4a09e334043..d6c904547ed9 100644 --- a/packages/server-utils/src/ai/anthropic-ai/index.ts +++ b/packages/server-utils/src/ai/anthropic-ai/index.ts @@ -22,6 +22,7 @@ import { GEN_AI_RESPONSE_TEXT, GEN_AI_RESPONSE_TOOL_CALLS, GEN_AI_TOOL_DEFINITIONS, + SENTRY_OP, } from '@sentry/conventions/attributes'; import { GEN_AI_REQUEST_STREAM_ATTRIBUTE } from '../core/gen-ai-attributes'; import type { InstrumentedMethodEntry } from '../core/utils'; @@ -177,7 +178,7 @@ function handleStreamingRequest( target: (...args: T) => R | Promise, invocationThis: unknown, args: T, - spanConfig: { name: string; op: string; attributes: Record }, + spanConfig: { name: string; attributes: Record }, params: Record | undefined, options: AnthropicAiOptions, isStreamRequested: boolean, @@ -268,8 +269,10 @@ function instrumentMethod( (typeof model === 'string' && model !== 'unknown') || !(client && hasSpanStreamingEnabled(client)) ? `${operationName} ${model}` : operationName, - op: getGenAiSpanOp(operationName), - attributes: requestAttributes as Record, + attributes: { + [SENTRY_OP]: getGenAiSpanOp(operationName), + ...(requestAttributes as Record), + }, }; const params = typeof args[0] === 'object' ? (args[0] as Record) : undefined; diff --git a/packages/server-utils/src/ai/google-genai/index.ts b/packages/server-utils/src/ai/google-genai/index.ts index fc64222a291f..7e003487427b 100644 --- a/packages/server-utils/src/ai/google-genai/index.ts +++ b/packages/server-utils/src/ai/google-genai/index.ts @@ -31,6 +31,7 @@ import { GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_TOTAL_TOKENS, + SENTRY_OP, } from '@sentry/conventions/attributes'; import type { InstrumentedMethodEntry } from '../core/utils'; import { buildMethodPath, extractSystemInstructions, getGenAiSpanOp, resolveAIRecordingOptions } from '../core/utils'; @@ -316,8 +317,10 @@ function instrumentMethod( return startSpanManual( { name: spanName, - op: getGenAiSpanOp(operationName), - attributes: requestAttributes, + attributes: { + [SENTRY_OP]: getGenAiSpanOp(operationName), + ...requestAttributes, + }, }, async (span: Span) => { try { @@ -338,8 +341,10 @@ function instrumentMethod( return startSpan( { name: spanName, - op: getGenAiSpanOp(operationName), - attributes: requestAttributes, + attributes: { + [SENTRY_OP]: getGenAiSpanOp(operationName), + ...requestAttributes, + }, }, (span: Span) => { if (options.recordInputs && attributeParams) { diff --git a/packages/server-utils/src/ai/openai/index.ts b/packages/server-utils/src/ai/openai/index.ts index 701456f5f2af..1b37a685c12f 100644 --- a/packages/server-utils/src/ai/openai/index.ts +++ b/packages/server-utils/src/ai/openai/index.ts @@ -19,6 +19,7 @@ import { GEN_AI_REQUEST_MODEL, GEN_AI_SYSTEM_INSTRUCTIONS, GEN_AI_TOOL_DEFINITIONS, + SENTRY_OP, } from '@sentry/conventions/attributes'; import type { InstrumentedMethodEntry } from '../core/utils'; import { @@ -153,8 +154,10 @@ function instrumentMethod( model !== 'unknown' || !(client && hasSpanStreamingEnabled(client)) ? `${operationName} ${model}` : operationName, - op: getGenAiSpanOp(operationName), - attributes: requestAttributes as Record, + attributes: { + [SENTRY_OP]: getGenAiSpanOp(operationName), + ...(requestAttributes as Record), + }, }; if (isStreamRequested) { diff --git a/packages/server-utils/src/integrations/anthropic.ts b/packages/server-utils/src/integrations/anthropic.ts index b54223925494..bbcfe2ad978d 100644 --- a/packages/server-utils/src/integrations/anthropic.ts +++ b/packages/server-utils/src/integrations/anthropic.ts @@ -1,4 +1,4 @@ -import { GEN_AI_REQUEST_MODEL } from '@sentry/conventions/attributes'; +import { GEN_AI_REQUEST_MODEL, SENTRY_OP } from '@sentry/conventions/attributes'; import * as diagnosticsChannel from 'node:diagnostics_channel'; import type { IntegrationFn, Span, SpanAttributeValue } from '@sentry/core'; import { @@ -106,8 +106,10 @@ function createGenAiSpan( const span = startInactiveSpan({ // With span streaming, omit the `'unknown'` model sentinel so the name stays low-cardinality. name: model !== 'unknown' || !(client && hasSpanStreamingEnabled(client)) ? `${operation} ${model}` : operation, - op: getGenAiSpanOp(operation), - attributes: attributes as Record, + attributes: { + [SENTRY_OP]: getGenAiSpanOp(operation), + ...(attributes as Record), + }, }); if (recordInputs && params) { diff --git a/packages/server-utils/src/integrations/aws-sdk/index.ts b/packages/server-utils/src/integrations/aws-sdk/index.ts index 1b0656551a10..20ee67e9d821 100644 --- a/packages/server-utils/src/integrations/aws-sdk/index.ts +++ b/packages/server-utils/src/integrations/aws-sdk/index.ts @@ -2,11 +2,12 @@ import * as diagnosticsChannel from 'node:diagnostics_channel'; import type { IntegrationFn, Span } from '@sentry/core'; import { defineIntegration, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startInactiveSpan } from '@sentry/core'; import { - _AWS_REQUEST_ID as AWS_REQUEST_ID, AWS_REQUEST_EXTENDED_ID, CLOUD_REGION, - SENTRY_KIND, HTTP_RESPONSE_STATUS_CODE, + SENTRY_KIND, + SENTRY_OP, + _AWS_REQUEST_ID as AWS_REQUEST_ID, } from '@sentry/conventions/attributes'; import { RPC } from '@sentry/conventions/op'; import { CHANNELS } from '../../orchestrion/channels'; @@ -108,8 +109,8 @@ function instrumentAwsSdk(servicesExtensions: ServicesExtensions): void { name: requestMetadata.spanName ?? `${normalizedRequest.serviceName}.${normalizedRequest.commandName}`, // `rpc` matches what the exporter infers from `rpc.service` for the OTel aws-sdk spans; // service extensions override it where inference yields a different op (DynamoDB: `db`). - op: requestMetadata.spanOp || RPC, attributes: { + [SENTRY_OP]: requestMetadata.spanOp || RPC, [SENTRY_KIND]: 'client', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: AWS_SDK_ORIGIN, ...extractAttributesFromNormalizedRequest(normalizedRequest), diff --git a/packages/server-utils/src/integrations/google-genai.ts b/packages/server-utils/src/integrations/google-genai.ts index 4a67a2f4d72d..a35663de0a76 100644 --- a/packages/server-utils/src/integrations/google-genai.ts +++ b/packages/server-utils/src/integrations/google-genai.ts @@ -122,8 +122,10 @@ function createGenAiSpan( const span = startInactiveSpan({ // With span streaming, omit the `'unknown'` model sentinel so the name stays low-cardinality. name: model !== 'unknown' || !(client && hasSpanStreamingEnabled(client)) ? `${operation} ${model}` : operation, - op: getGenAiSpanOp(operation), - attributes, + attributes: { + [SENTRY_OP]: getGenAiSpanOp(operation), + ...attributes, + }, }); if (recordInputs && params) { diff --git a/packages/server-utils/src/integrations/knex.ts b/packages/server-utils/src/integrations/knex.ts index d23037b70b0a..201a56f09077 100644 --- a/packages/server-utils/src/integrations/knex.ts +++ b/packages/server-utils/src/integrations/knex.ts @@ -188,7 +188,10 @@ function subscribeQuery(): void { return startInactiveSpan({ name: dbStatement ?? getName(name, operation, table) ?? 'knex.query', parentSpan, - attributes, + attributes: { + [SENTRY_OP]: 'db', + ...attributes, + }, }); }, { diff --git a/packages/server-utils/src/integrations/openai.ts b/packages/server-utils/src/integrations/openai.ts index 6e900596aef4..adbf95c4d53e 100644 --- a/packages/server-utils/src/integrations/openai.ts +++ b/packages/server-utils/src/integrations/openai.ts @@ -1,3 +1,4 @@ +import { SENTRY_OP } from '@sentry/conventions/attributes'; import * as diagnosticsChannel from 'node:diagnostics_channel'; import type { IntegrationFn, Span, SpanAttributeValue } from '@sentry/core'; import { @@ -89,8 +90,10 @@ function createGenAiSpan(data: OpenAiChatChannelContext, operation: string, opti const span = startInactiveSpan({ // With span streaming, omit the `'unknown'` model sentinel so the name stays low-cardinality. name: model !== 'unknown' || !(client && hasSpanStreamingEnabled(client)) ? `${operation} ${model}` : operation, - op: getGenAiSpanOp(operation), - attributes: attributes as Record, + attributes: { + [SENTRY_OP]: getGenAiSpanOp(operation), + ...(attributes as Record), + }, }); if (recordInputs && params) { diff --git a/packages/server-utils/test/ai/lib/tracing/langchain-embeddings.test.ts b/packages/server-utils/test/ai/lib/tracing/langchain-embeddings.test.ts index 0c4dc6b632eb..130800f2f6d7 100644 --- a/packages/server-utils/test/ai/lib/tracing/langchain-embeddings.test.ts +++ b/packages/server-utils/test/ai/lib/tracing/langchain-embeddings.test.ts @@ -6,6 +6,7 @@ import { GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, + SENTRY_OP, } from '@sentry/conventions/attributes'; import { GEN_AI_EMBEDDINGS } from '@sentry/conventions/op'; import { diff --git a/packages/sveltekit/test/client/browserTracingIntegration.test.ts b/packages/sveltekit/test/client/browserTracingIntegration.test.ts index 587af53544af..9b1e0c6e925f 100644 --- a/packages/sveltekit/test/client/browserTracingIntegration.test.ts +++ b/packages/sveltekit/test/client/browserTracingIntegration.test.ts @@ -114,7 +114,7 @@ describe('browserTracingIntegration', () => { expect(startBrowserTracingPageLoadSpanSpy).toHaveBeenCalledWith(fakeClient, { name: '/', attributes: { - 'sentry.op': 'pageload', + [SENTRY_OP]: 'pageload', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.sveltekit', [SENTRY_SEGMENT_NAME_SOURCE]: 'url', }, @@ -218,7 +218,7 @@ describe('browserTracingIntegration', () => { { name: '/users/[id]', attributes: { - 'sentry.op': 'navigation', + [SENTRY_OP]: 'navigation', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.sveltekit', [SENTRY_SEGMENT_NAME_SOURCE]: 'route', [URL_TEMPLATE]: '/users/[id]', @@ -362,7 +362,7 @@ describe('browserTracingIntegration', () => { { name: '/users/[id]', attributes: { - 'sentry.op': 'navigation', + [SENTRY_OP]: 'navigation', [SENTRY_SEGMENT_NAME_SOURCE]: 'route', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.sveltekit', [URL_TEMPLATE]: '/users/[id]', diff --git a/packages/sveltekit/test/client/svelte5BrowserTracing.test.ts b/packages/sveltekit/test/client/svelte5BrowserTracing.test.ts index f77d2c0f8195..37e186ebe810 100644 --- a/packages/sveltekit/test/client/svelte5BrowserTracing.test.ts +++ b/packages/sveltekit/test/client/svelte5BrowserTracing.test.ts @@ -6,7 +6,7 @@ import type { Span } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import * as SentrySvelte from '@sentry/svelte'; -import { SENTRY_SEGMENT_NAME_SOURCE, URL_TEMPLATE } from '@sentry/conventions/attributes'; +import { SENTRY_OP, SENTRY_SEGMENT_NAME_SOURCE, URL_TEMPLATE } from '@sentry/conventions/attributes'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { instrumentSvelteKitTracing } from '../../src/client/svelte5BrowserTracing'; @@ -82,7 +82,7 @@ describe('svelte5 browser tracing', () => { expect(startPageLoadSpanSpy).toHaveBeenCalledWith(client, { name: '/', attributes: { - 'sentry.op': 'pageload', + [SENTRY_OP]: 'pageload', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.sveltekit', [SENTRY_SEGMENT_NAME_SOURCE]: 'url', }, @@ -123,7 +123,7 @@ describe('svelte5 browser tracing', () => { expect.objectContaining({ name: '/users/[id]', attributes: expect.objectContaining({ - 'sentry.op': 'navigation', + [SENTRY_OP]: 'navigation', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.sveltekit', [SENTRY_SEGMENT_NAME_SOURCE]: 'route', [URL_TEMPLATE]: '/users/[id]', diff --git a/packages/vue/src/router.ts b/packages/vue/src/router.ts index 7792a801b51e..74afc5205b6a 100644 --- a/packages/vue/src/router.ts +++ b/packages/vue/src/router.ts @@ -151,6 +151,7 @@ export function instrumentVueRouter( { name: isUnparameterizedStreamedNavigation ? NAVIGATION_SPAN_NAME_FALLBACK : spanName, attributes: { + [SENTRY_OP]: 'navigation', ...attributes, [SENTRY_OP]: NAVIGATION, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.vue', From 2fa39b21a47130abf7fb5e29112fcf708a99472f Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 1 Sep 2026 11:47:05 +0200 Subject: [PATCH 3/4] deslop route span creation --- .../src/tracing/browserTracingIntegration.ts | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts index 54b442c63e62..ec25281a6448 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -331,20 +331,12 @@ export const browserTracingIntegration = ((options: Partial Date: Tue, 1 Sep 2026 11:58:00 +0200 Subject: [PATCH 4/4] further deslop --- packages/astro/src/server/middleware.ts | 4 ++-- .../src/tracing/browserTracingIntegration.ts | 3 +-- packages/core/src/types/startSpanOptions.ts | 13 +++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index 7c2f744480f9..347f8d4add30 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -108,7 +108,7 @@ export const handleRequest: (options?: MiddlewareOptions) => MiddlewareHandler = const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined; // if there is an active span, we just want to enhance it with routing data etc. - if (rootSpan && spanToJSON(rootSpan).attributes[SENTRY_OP] === 'http.server') { + if (rootSpan && spanToJSON(rootSpan).attributes[SENTRY_OP] === HTTP_SERVER) { return enhanceHttpServerSpan(ctx, next, rootSpan); } @@ -253,7 +253,7 @@ async function instrumentRequestStartHttpServerSpan( const res = await startSpan( { attributes: { - [SENTRY_OP]: 'http.server', + [SENTRY_OP]: HTTP_SERVER, ...attributes, }, name, diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts index ec25281a6448..dc19c94e8141 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -5,7 +5,6 @@ import type { RequestHookInfo, ResponseHookInfo, Span, - SpanAttributes, StartSpanOptions, } from '@sentry/core/browser'; import { @@ -345,7 +344,7 @@ export const browserTracingIntegration = ((options: Partial { - * Sentry.startSpan({ name: 'span-that-should-be-a-segment' }, () => { + * Sentry.startSpan({ name: 'span-that-should-be-a-root' }, () => { * // ... * }); * }); * ``` * - * @example Keeping that segment span attached to an incoming trace + * @example Keeping the root span attached to a specific trace: * ```js * Sentry.continueTrace({ sentryTrace, baggage }, () => * Sentry.withActiveSpan(null, () => - * Sentry.startSpan({ name: 'span-that-should-be-a-segment' }, () => { + * Sentry.startSpan({ name: 'span-that-should-be-a-root' }, () => { * // ... * }), * ),