diff --git a/.cursor/BUGBOT.md b/.cursor/BUGBOT.md index a512d79fa435..d70f36ff6c94 100644 --- a/.cursor/BUGBOT.md +++ b/.cursor/BUGBOT.md @@ -40,3 +40,4 @@ Do not flag the issues below if they appear in tests. - If there's no direct span that's wrapping the captured exception, apply a proper `type` value, following the same naming convention as the `SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN` value. - When calling `startSpan`, check if error cases are handled. If flag that it might make sense to try/catch and call `captureException`. +- When calling `generateInstrumentationOnce`, the passed in name MUST match the name of the integration that uses it. If there are more than one instrumentations, they need to follow the pattern `${INSTRUMENTATION_NAME}.some-suffix`. diff --git a/packages/nestjs/src/integrations/nest.ts b/packages/nestjs/src/integrations/nest.ts index 53086b7da302..75dc1f845693 100644 --- a/packages/nestjs/src/integrations/nest.ts +++ b/packages/nestjs/src/integrations/nest.ts @@ -6,15 +6,15 @@ import { SentryNestInstrumentation } from './sentry-nest-instrumentation'; const INTEGRATION_NAME = 'Nest'; -const instrumentNestCore = generateInstrumentOnce('Nest-Core', () => { +const instrumentNestCore = generateInstrumentOnce(`${INTEGRATION_NAME}.Core`, () => { return new NestInstrumentationCore(); }); -const instrumentNestCommon = generateInstrumentOnce('Nest-Common', () => { +const instrumentNestCommon = generateInstrumentOnce(`${INTEGRATION_NAME}.Common`, () => { return new SentryNestInstrumentation(); }); -const instrumentNestEvent = generateInstrumentOnce('Nest-Event', () => { +const instrumentNestEvent = generateInstrumentOnce(`${INTEGRATION_NAME}.Event`, () => { return new SentryNestEventInstrumentation(); }); diff --git a/packages/node/src/integrations/tracing/fastify/index.ts b/packages/node/src/integrations/tracing/fastify/index.ts index fd8894e29a96..65d783eb8be7 100644 --- a/packages/node/src/integrations/tracing/fastify/index.ts +++ b/packages/node/src/integrations/tracing/fastify/index.ts @@ -90,10 +90,11 @@ interface FastifyHandlerOptions { } const INTEGRATION_NAME = 'Fastify'; -const INTEGRATION_NAME_V5 = 'Fastify-V5'; -const INTEGRATION_NAME_V3 = 'Fastify-V3'; -export const instrumentFastifyV3 = generateInstrumentOnce(INTEGRATION_NAME_V3, () => new FastifyInstrumentationV3()); +export const instrumentFastifyV3 = generateInstrumentOnce( + `${INTEGRATION_NAME}.v3`, + () => new FastifyInstrumentationV3(), +); function getFastifyIntegration(): ReturnType | undefined { const client = getClient(); @@ -135,7 +136,7 @@ function handleFastifyError( } } -export const instrumentFastify = generateInstrumentOnce(INTEGRATION_NAME_V5, () => { +export const instrumentFastify = generateInstrumentOnce(`${INTEGRATION_NAME}.v5`, () => { const fastifyOtelInstrumentationInstance = new FastifyOtelInstrumentation(); const plugin = fastifyOtelInstrumentationInstance.plugin(); diff --git a/packages/node/src/integrations/tracing/redis.ts b/packages/node/src/integrations/tracing/redis.ts index 308c8be29abe..8376c99c1998 100644 --- a/packages/node/src/integrations/tracing/redis.ts +++ b/packages/node/src/integrations/tracing/redis.ts @@ -75,13 +75,13 @@ const cacheResponseHook: RedisResponseCustomAttributeFunction = (span: Span, red span.updateName(truncate(spanDescription, 1024)); }; -const instrumentIORedis = generateInstrumentOnce('IORedis', () => { +const instrumentIORedis = generateInstrumentOnce(`${INTEGRATION_NAME}.IORedis`, () => { return new IORedisInstrumentation({ responseHook: cacheResponseHook, }); }); -const instrumentRedisModule = generateInstrumentOnce('Redis', () => { +const instrumentRedisModule = generateInstrumentOnce(`${INTEGRATION_NAME}.Redis`, () => { return new RedisInstrumentation({ responseHook: cacheResponseHook, }); diff --git a/packages/node/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts index fc6b02c3830d..ef27be0514c3 100644 --- a/packages/node/src/sdk/initOtel.ts +++ b/packages/node/src/sdk/initOtel.ts @@ -101,7 +101,11 @@ function getPreloadMethods(integrationNames?: string[]): ((() => void) & { id: s return instruments; } - return instruments.filter(instrumentation => integrationNames.includes(instrumentation.id)); + // We match exact matches of instrumentation, but also match prefixes, e.g. "Fastify.v5" will match "Fastify" + return instruments.filter(instrumentation => { + const id = instrumentation.id; + return integrationNames.some(integrationName => id === integrationName || id.startsWith(`${integrationName}.`)); + }); } /** Just exported for tests. */ diff --git a/packages/node/test/sdk/preload.test.ts b/packages/node/test/sdk/preload.test.ts index 97badc28c9eb..65e61287bd33 100644 --- a/packages/node/test/sdk/preload.test.ts +++ b/packages/node/test/sdk/preload.test.ts @@ -1,10 +1,27 @@ import { debug } from '@sentry/core'; -import { afterEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { resetGlobals } from '../helpers/mockSdkInit'; describe('preload', () => { + beforeEach(() => { + // Mock this to prevent conflicts with other tests + vi.mock('../../src/integrations/tracing', async (importOriginal: () => Promise>) => { + const actual = await importOriginal(); + return { + ...actual, + getOpenTelemetryInstrumentationToPreload: () => [ + Object.assign(vi.fn(), { id: 'Http.sentry' }), + Object.assign(vi.fn(), { id: 'Http' }), + Object.assign(vi.fn(), { id: 'Express' }), + Object.assign(vi.fn(), { id: 'Graphql' }), + ], + }; + }); + }); + afterEach(() => { - vi.resetAllMocks(); debug.disable(); + resetGlobals(); delete process.env.SENTRY_DEBUG; delete process.env.SENTRY_PRELOAD_INTEGRATIONS; @@ -29,6 +46,7 @@ describe('preload', () => { await import('../../src/preload'); + expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Http.sentry instrumentation'); expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Http instrumentation'); expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Express instrumentation'); expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Graphql instrumentation'); @@ -44,6 +62,7 @@ describe('preload', () => { await import('../../src/preload'); + expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Http.sentry instrumentation'); expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Http instrumentation'); expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Express instrumentation'); expect(logSpy).not.toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Graphql instrumentation'); diff --git a/packages/react-router/src/server/integration/reactRouterServer.ts b/packages/react-router/src/server/integration/reactRouterServer.ts index 89a0443c2382..4625d1cb979e 100644 --- a/packages/react-router/src/server/integration/reactRouterServer.ts +++ b/packages/react-router/src/server/integration/reactRouterServer.ts @@ -5,7 +5,7 @@ import { ReactRouterInstrumentation } from '../instrumentation/reactRouter'; const INTEGRATION_NAME = 'ReactRouterServer'; -const instrumentReactRouter = generateInstrumentOnce('React-Router-Server', () => { +const instrumentReactRouter = generateInstrumentOnce(INTEGRATION_NAME, () => { return new ReactRouterInstrumentation(); });