Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ test('Sends an app_creation transaction', async () => {
contexts: { trace: { op?: string; origin?: string; data?: Record<string, unknown> } };
};

expect(transaction.contexts.trace.op).toBe('app_creation.nestjs');
expect(transaction.contexts.trace.op).toBe('function');
expect(transaction.contexts.trace.origin).toBe('auto.http.nestjs');
expect(transaction.contexts.trace.data).toEqual(
expect.objectContaining({
component: '@nestjs/core',
'nestjs.type': 'app_creation',
'nestjs.module': 'AppModule',
'sentry.op': 'app_creation.nestjs',
'sentry.op': 'function',
'sentry.origin': 'auto.http.nestjs',
}),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ test('Event emitter', async () => {
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
data: {
'sentry.source': 'custom',
'sentry.op': 'event.nestjs',
'sentry.op': 'function',
'sentry.origin': 'auto.event.nestjs',
},
origin: 'auto.event.nestjs',
op: 'event.nestjs',
op: 'function',
status: 'ok',
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ test.skip('Sends an API route transaction', async ({ baseURL }) => {
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
data: {
'sentry.origin': 'auto.http.nestjs',
'sentry.op': 'request_context.nestjs',
'sentry.op': 'function',
component: '@nestjs/core',
'nestjs.version': expect.any(String),
'nestjs.type': 'request_context',
Expand All @@ -117,7 +117,7 @@ test.skip('Sends an API route transaction', async ({ baseURL }) => {
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
status: 'ok',
op: 'request_context.nestjs',
op: 'function',
origin: 'auto.http.nestjs',
},
{
Expand Down Expand Up @@ -798,8 +798,16 @@ test('Sets error status on nest spans when a handler throws', async ({ baseURL }

expect(transactionEvent.spans).toEqual(
expect.arrayContaining([
expect.objectContaining({ op: 'request_context.nestjs', status: 'internal_error' }),
expect.objectContaining({ op: 'handler', status: 'internal_error' }),
expect.objectContaining({
op: 'function',
status: 'internal_error',
data: expect.objectContaining({ 'nestjs.type': 'request_context' }),
}),
expect.objectContaining({
op: 'handler',
status: 'internal_error',
data: expect.objectContaining({ 'nestjs.type': 'handler' }),
}),
]),
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ test('@OnEvent opens an event.nestjs transaction', async ({ baseURL }) => {
await fetch(`${baseURL}/test-event`);
const transactionEvent = await transactionPromise;

expect(transactionEvent.contexts?.trace?.op).toBe('event.nestjs');
expect(transactionEvent.contexts?.trace?.op).toBe('function');
expect(transactionEvent.contexts?.trace?.origin).toBe('auto.event.nestjs');
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ test('app_creation: emits a "Create Nest App" transaction at startup', async ()
contexts: { trace: { op?: string; origin?: string; data?: Record<string, unknown> } };
};

expect(transaction.contexts.trace.op).toBe('app_creation.nestjs');
expect(transaction.contexts.trace.op).toBe('function');
expect(transaction.contexts.trace.origin).toBe('auto.http.nestjs');
expect(transaction.contexts.trace.data).toEqual(
expect.objectContaining({
Expand All @@ -47,10 +47,13 @@ test('request_context + handler: a route transaction nests the nestjs spans', as
await fetch(`${baseURL}/test-transaction`);
const transactionEvent = await transactionPromise;

// request_context span, identified by its controller/callback attributes.
// request_context span, identified by its `nestjs.type` attribute — it shares
// the `function` op and `auto.http.nestjs` origin with the request_handler span.
// Its description isn't asserted: the span carries `http.*` attributes, so
// the OTel span-name inference rewrites it to `GET /test-transaction`
const requestContext = findSpan(transactionEvent, 'request_context.nestjs', 'auto.http.nestjs');
const requestContext = (transactionEvent.spans ?? []).find(
span => span.data?.['nestjs.type'] === 'request_context' && span.origin === 'auto.http.nestjs',
);
expect(requestContext).toBeDefined();
expect(requestContext?.data).toMatchObject({
'nestjs.type': 'request_context',
Expand Down
4 changes: 2 additions & 2 deletions packages/nestjs/src/integrations/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SENTRY_OP } from '@sentry/conventions/attributes';
import { WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op';
import { WEB_SERVER_FUNCTION_SPAN_OP, WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/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.

Wrong function span op constant

High Severity

This migration imports WEB_SERVER_FUNCTION_SPAN_OP, but the cross-framework function op constant used elsewhere in this repo is GENERAL_FUNCTION_SPAN_OP (see Angular and Cloudflare). function is a general-category op, not a web-server one, so this constant name does not match the intended shared convention and is not used anywhere else in the monorepo.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fed2041. Configure here.

import type { Span } from '@sentry/core';
import {
addNonEnumerableProperty,
Expand Down Expand Up @@ -108,7 +108,7 @@ export function getEventSpanOptions(event: string): {
return {
name: `event ${event}`,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'event.nestjs',
[SENTRY_OP]: WEB_SERVER_FUNCTION_SPAN_OP,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.event.nestjs',
},
forceTransaction: true,
Expand Down
14 changes: 7 additions & 7 deletions packages/nestjs/src/integrations/wrap-route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HTTP_METHOD, HTTP_ROUTE, SENTRY_OP, URL_FULL } from '@sentry/conventions/attributes';
import { WEB_SERVER_FUNCTION_SPAN_OP } from '@sentry/conventions/op';
import type { SpanAttributes } from '@sentry/core';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core';
import type { AnyFn } from './helpers';
Expand All @@ -23,16 +24,16 @@ interface NestRequest {
url?: string;
}

/** Span options for the `Create Nest App` (app_creation) span. */
/** Span options for the `Create Nest App` (app_creation, `function` op) span. */
export function getAppCreationSpanOptions(
moduleVersion?: string,
moduleName?: string,
): { name: string; op: string; attributes: SpanAttributes } {
): { name: string; attributes: SpanAttributes } {
return {
name: 'Create Nest App',
op: `${NestType.APP_CREATION}.nestjs`,
attributes: {
component: NESTJS_COMPONENT,
[SENTRY_OP]: WEB_SERVER_FUNCTION_SPAN_OP,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: HTTP_ORIGIN,
[AttributeNames.TYPE]: NestType.APP_CREATION,
[AttributeNames.VERSION]: moduleVersion || undefined,
Expand Down Expand Up @@ -73,7 +74,7 @@ export function wrapRouteHandler(callback: AnyFn, moduleVersion?: string): AnyFn

/**
* Wrap the per-request handler that `RouterExecutionContext.create` returns so
* each request opens the `request_context.nestjs` span (REQUEST_CONTEXT),
* each request opens the request-context (`function` op) span (REQUEST_CONTEXT),
* carrying controller/callback names plus the per-request http.* attributes.
*/
export function wrapRequestContextHandler(
Expand All @@ -89,6 +90,7 @@ export function wrapRequestContextHandler(
const attributes: SpanAttributes = {
component: NESTJS_COMPONENT,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: HTTP_ORIGIN,
[SENTRY_OP]: WEB_SERVER_FUNCTION_SPAN_OP,
[AttributeNames.TYPE]: NestType.REQUEST_CONTEXT,
[AttributeNames.CONTROLLER]: instanceName,
[AttributeNames.CALLBACK]: callbackName,
Expand All @@ -98,9 +100,7 @@ export function wrapRequestContextHandler(
[HTTP_METHOD]: req.method || undefined,
[URL_FULL]: req.originalUrl || req.url || undefined,
};
return startSpan({ name: spanName, op: `${NestType.REQUEST_CONTEXT}.nestjs`, attributes }, () =>
handler.apply(this, handlerArgs),
);
return startSpan({ name: spanName, attributes }, () => handler.apply(this, handlerArgs));
};
markWrapped(wrapped);
return wrapped;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ describe('NestJS orchestrion subscriber: app_creation', () => {
expect(span).toBeDefined();
const json = spanToJSON(span!);
expect(json.description).toBe('Create Nest App');
expect(json.op).toBe('app_creation.nestjs');
expect(json.op).toBe('function');
expect(json.origin).toBe('auto.http.nestjs');
expect(json.data).toMatchObject({
component: '@nestjs/core',
Expand Down Expand Up @@ -241,7 +241,7 @@ describe('NestJS orchestrion subscriber: request_context / request_handler', ()

expect(contextSpanJson).toBeDefined();
expect(contextSpanJson!.description).toBe('CatsController.getCats');
expect(contextSpanJson!.op).toBe('request_context.nestjs');
expect(contextSpanJson!.op).toBe('function');
expect(contextSpanJson!.origin).toBe('auto.http.nestjs');
expect(contextSpanJson!.data).toMatchObject({
component: '@nestjs/core',
Expand Down Expand Up @@ -872,7 +872,7 @@ describe('NestJS orchestrion subscriber: schedule / event / bullmq', () => {

const json = spanToJSON(spanInside!);
expect(json.description).toBe('event user.created');
expect(json.op).toBe('event.nestjs');
expect(json.op).toBe('function');
expect(json.origin).toBe('auto.event.nestjs');
});

Expand Down
Loading