Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/astro/src/server/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -252,7 +252,10 @@ async function instrumentRequestStartHttpServerSpan(

const res = await startSpan(
{
attributes,
attributes: {
[SENTRY_OP]: HTTP_SERVER,
...attributes,
},
name,
},
async span => {
Expand Down
11 changes: 7 additions & 4 deletions packages/browser-utils/src/performance/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
});
Expand Down Expand Up @@ -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',
};

Expand All @@ -180,7 +181,6 @@ export function startTrackingLongAnimationFrames(): void {

startAndEndSpan(parent, startTime, startTime + duration, {
name: 'Main UI thread blocked',
op: UI_LONG_ANIMATION_FRAME,
attributes,
});
}
Expand Down Expand Up @@ -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;
Expand All @@ -474,7 +478,6 @@ export function _addResourceSpans(
name: spanStreamingEnabled
? domain || RESOURCE_SPAN_NAME_FALLBACK
: resourceUrl.replace(WINDOW.location.origin, ''),
op,
attributes: attributesWithResourceTiming,
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/browser-utils/src/performance/userTiming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
};

Expand All @@ -130,7 +131,6 @@ export function _addUserTimingSpan(
if (spanStartTimestamp <= spanEndTimestamp) {
startAndEndSpan(parentSpan, spanStartTimestamp, spanEndTimestamp, {
name: entry.name,
op: entry.entryType,
attributes,
});
}
Expand Down
24 changes: 16 additions & 8 deletions packages/browser/src/tracing/browserTracingIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,14 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption

/** Create routing idle transaction. */
function _createRouteSpan(client: Client, startSpanOptions: StartSpanOptions, makeActive = true, url?: string): void {
const isPageloadSpan = startSpanOptions.op === 'pageload';
const originalOp = startSpanOptions.attributes?.[SENTRY_OP];
// backfill top-level `op` option
// oxlint-disable-next-line typescript/no-deprecated
const optionsWithOp: StartSpanOptions = { op: originalOp, ...startSpanOptions };
const isPageloadSpan = originalOp === PAGELOAD;

const initialSpanName = startSpanOptions.name;
const finalStartSpanOptions: StartSpanOptions = beforeStartSpan
? beforeStartSpan(startSpanOptions)
: startSpanOptions;
const initialSpanName = optionsWithOp.name;
const finalStartSpanOptions: StartSpanOptions = beforeStartSpan ? beforeStartSpan(optionsWithOp) : optionsWithOp;

// For navigations, `url` is the destination URL, so we use it to reflect the post-navigation location.
// For pageloads (and manual navigation spans without a URL) we fall back to the current location.
Expand All @@ -348,6 +350,12 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption
...finalStartSpanOptions.attributes,
};

// oxlint-disable-next-line typescript/no-deprecated
if (finalStartSpanOptions.op !== originalOp) {
// oxlint-disable-next-line typescript/no-deprecated
attributes[SENTRY_OP] = finalStartSpanOptions.op;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beforeStartSpan can drop span op

Medium Severity

The beforeStartSpan backfill writes finalStartSpanOptions.op onto sentry.op whenever it differs from the original attribute. Omitting the deprecated op field (or setting both, which the deprecation says should prefer the attribute) overwrites or deletes sentry.op, so pageload and navigation spans can lose their op.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit dbe044c. Configure here.


// If `finalStartSpanOptions.name` is different than `startSpanOptions.name`
// it is because `beforeStartSpan` set a custom name. Therefore we set the source to 'custom'.
if (initialSpanName !== finalStartSpanOptions.name) {
Expand Down Expand Up @@ -485,8 +493,8 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption
_createRouteSpan(
client,
{
op: NAVIGATION_REDIRECT,
...startSpanOptions,
attributes: { [SENTRY_OP]: NAVIGATION_REDIRECT, ...startSpanOptions.attributes },
},
false,
navigationOptions.url,
Expand Down Expand Up @@ -517,8 +525,8 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption
_createRouteSpan(
client,
{
op: NAVIGATION,
...startSpanOptions,
attributes: { [SENTRY_OP]: NAVIGATION, ...startSpanOptions.attributes },
// Navigation starts a new trace and is NOT parented under any active interaction (e.g. ui.action.click)
parentSpan: null,
},
Expand Down Expand Up @@ -555,8 +563,8 @@ export const browserTracingIntegration = ((options: Partial<BrowserTracingOption
});

_createRouteSpan(client, {
op: PAGELOAD,
...startSpanOptions,
attributes: { [SENTRY_OP]: PAGELOAD, ...startSpanOptions.attributes },
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/integrations/mcp-server/spans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ export function buildMcpServerSpanConfig(

return {
name: spanName,
// oxlint-disable-next-line typescript/no-deprecated
forceTransaction: true,
attributes,
};
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/tracing/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
14 changes: 13 additions & 1 deletion packages/core/src/types/startSpanOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down
1 change: 0 additions & 1 deletion packages/nestjs/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
3 changes: 0 additions & 3 deletions packages/nestjs/test/decorators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
9 changes: 6 additions & 3 deletions packages/server-utils/src/ai/anthropic-ai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -177,7 +178,7 @@ function handleStreamingRequest<T extends unknown[], R>(
target: (...args: T) => R | Promise<R>,
invocationThis: unknown,
args: T,
spanConfig: { name: string; op: string; attributes: Record<string, SpanAttributeValue> },
spanConfig: { name: string; attributes: Record<string, SpanAttributeValue> },
params: Record<string, unknown> | undefined,
options: AnthropicAiOptions,
isStreamRequested: boolean,
Expand Down Expand Up @@ -268,8 +269,10 @@ function instrumentMethod<T extends unknown[], R>(
(typeof model === 'string' && model !== 'unknown') || !(client && hasSpanStreamingEnabled(client))
? `${operationName} ${model}`
: operationName,
op: getGenAiSpanOp(operationName),
attributes: requestAttributes as Record<string, SpanAttributeValue>,
attributes: {
[SENTRY_OP]: getGenAiSpanOp(operationName),
...(requestAttributes as Record<string, SpanAttributeValue>),
},
};

const params = typeof args[0] === 'object' ? (args[0] as Record<string, unknown>) : undefined;
Expand Down
13 changes: 9 additions & 4 deletions packages/server-utils/src/ai/google-genai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -316,8 +317,10 @@ function instrumentMethod<T extends unknown[], R>(
return startSpanManual(
{
name: spanName,
op: getGenAiSpanOp(operationName),
attributes: requestAttributes,
attributes: {
[SENTRY_OP]: getGenAiSpanOp(operationName),
...requestAttributes,
},
},
async (span: Span) => {
try {
Expand All @@ -338,8 +341,10 @@ function instrumentMethod<T extends unknown[], R>(
return startSpan(
{
name: spanName,
op: getGenAiSpanOp(operationName),
attributes: requestAttributes,
attributes: {
[SENTRY_OP]: getGenAiSpanOp(operationName),
...requestAttributes,
},
},
(span: Span) => {
if (options.recordInputs && attributeParams) {
Expand Down
7 changes: 5 additions & 2 deletions packages/server-utils/src/ai/openai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -153,8 +154,10 @@ function instrumentMethod<T extends unknown[], R>(
model !== 'unknown' || !(client && hasSpanStreamingEnabled(client))
? `${operationName} ${model}`
: operationName,
op: getGenAiSpanOp(operationName),
attributes: requestAttributes as Record<string, SpanAttributeValue>,
attributes: {
[SENTRY_OP]: getGenAiSpanOp(operationName),
...(requestAttributes as Record<string, SpanAttributeValue>),
},
};

if (isStreamRequested) {
Expand Down
8 changes: 5 additions & 3 deletions packages/server-utils/src/integrations/anthropic.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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<string, SpanAttributeValue>,
attributes: {
[SENTRY_OP]: getGenAiSpanOp(operation),
...(attributes as Record<string, SpanAttributeValue>),
},
});

if (recordInputs && params) {
Expand Down
Loading
Loading