diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index d22d0c35be88..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); } @@ -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..dc19c94e8141 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -331,12 +331,14 @@ export const browserTracingIntegration = ((options: Partial { 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/cloudflare/src/instrumentations/instrumentDurableObjectSyncKvStorage.ts b/packages/cloudflare/src/instrumentations/instrumentDurableObjectSyncKvStorage.ts index c8b94d14fef4..f1f594aed121 100644 --- a/packages/cloudflare/src/instrumentations/instrumentDurableObjectSyncKvStorage.ts +++ b/packages/cloudflare/src/instrumentations/instrumentDurableObjectSyncKvStorage.ts @@ -1,5 +1,5 @@ -import type { SyncKvStorage } from '@cloudflare/workers-types'; import { SENTRY_OP } from '@sentry/conventions/attributes'; +import type { SyncKvStorage } from '@cloudflare/workers-types'; import { DB } from '@sentry/conventions/op'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core'; diff --git a/packages/cloudflare/src/instrumentations/instrumentSqlStorage.ts b/packages/cloudflare/src/instrumentations/instrumentSqlStorage.ts index e2bd0721117f..45880ad2cf32 100644 --- a/packages/cloudflare/src/instrumentations/instrumentSqlStorage.ts +++ b/packages/cloudflare/src/instrumentations/instrumentSqlStorage.ts @@ -1,5 +1,5 @@ -import type { SqlStorage } from '@cloudflare/workers-types'; import { SENTRY_OP } from '@sentry/conventions/attributes'; +import type { SqlStorage } from '@cloudflare/workers-types'; import { DB_QUERY } from '@sentry/conventions/op'; import { _INTERNAL_getSqlQuerySummary, diff --git a/packages/cloudflare/src/instrumentations/worker/instrumentR2.ts b/packages/cloudflare/src/instrumentations/worker/instrumentR2.ts index 7d6ad24ae3e2..12ccb2257623 100644 --- a/packages/cloudflare/src/instrumentations/worker/instrumentR2.ts +++ b/packages/cloudflare/src/instrumentations/worker/instrumentR2.ts @@ -58,7 +58,6 @@ function createSpanOptions(bindingName: string, r2Op: R2OperationKey, key?: stri const requestKey = Array.isArray(key) ? key.join(', ') : typeof key === 'string' ? key : undefined; return { - op, name: spanName, attributes: { [CLOUDFLARE_R2_OPERATION]: operation, diff --git a/packages/cloudflare/test/instrumentations/worker/instrumentR2.test.ts b/packages/cloudflare/test/instrumentations/worker/instrumentR2.test.ts index 73aa3d2bf72c..016d555e89f6 100644 --- a/packages/cloudflare/test/instrumentations/worker/instrumentR2.test.ts +++ b/packages/cloudflare/test/instrumentations/worker/instrumentR2.test.ts @@ -80,7 +80,6 @@ describe('instrumentR2Bucket', () => { 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 76b3ba4ffa3e..48ec574091c8 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, }, @@ -211,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 b52d62624f23..6e928802757f 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, @@ -442,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/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..d4e71ab365b5 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; /** @@ -39,6 +51,32 @@ 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 a parent span is active. + * If you do need the span to be a segment (root) span, follow the examples below:. + * + * @example Making a span a root span: + * ```js + * Sentry.withActiveSpan(null, () => { + * Sentry.startSpan({ name: 'span-that-should-be-a-root' }, () => { + * // ... + * }); + * }); + * ``` + * + * @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-root' }, () => { + * // ... + * }), + * ), + * ); + * ``` */ forceTransaction?: boolean; 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/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/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/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( 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',