diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/v6/index.ts b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/vercelai-6/index.ts similarity index 100% rename from dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/v6/index.ts rename to dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/vercelai-6/index.ts diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/v6/test.ts b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/vercelai-6/test.ts similarity index 98% rename from dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/v6/test.ts rename to dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/vercelai-6/test.ts index 65fb16e9ab1e..556f5babbe80 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/v6/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/vercelai-6/test.ts @@ -31,7 +31,7 @@ it('captures a transaction with Vercel AI v6 spans via @sentry/cloudflare vercel is_segment: false, attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'gen_ai.invoke_agent' }, - 'sentry.origin': { type: 'string', value: 'auto.vercelai.otel' }, + 'sentry.origin': { type: 'string', value: 'auto.vercelai.channel' }, [GEN_AI_OPERATION_NAME]: { type: 'string', value: 'invoke_agent' }, [GEN_AI_USAGE_INPUT_TOKENS]: { type: 'integer', value: 10 }, [GEN_AI_USAGE_OUTPUT_TOKENS]: { type: 'integer', value: 20 }, @@ -49,7 +49,7 @@ it('captures a transaction with Vercel AI v6 spans via @sentry/cloudflare vercel is_segment: false, attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'gen_ai.generate_content' }, - 'sentry.origin': { type: 'string', value: 'auto.vercelai.otel' }, + 'sentry.origin': { type: 'string', value: 'auto.vercelai.channel' }, [GEN_AI_OPERATION_NAME]: { type: 'string', value: 'generate_content' }, }), }, diff --git a/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/vercelai-6/vite.config.mts b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/vercelai-6/vite.config.mts new file mode 100644 index 000000000000..cee0e8c0b4b8 --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/vercelai-6/vite.config.mts @@ -0,0 +1,7 @@ +import { cloudflare } from '@cloudflare/vite-plugin'; +import { sentryCloudflareVitePlugin } from '@sentry/cloudflare/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [cloudflare(), sentryCloudflareVitePlugin({ autoInstrumentation: false })], +}); diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/v6/wrangler.jsonc b/dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/vercelai-6/wrangler.jsonc similarity index 100% rename from dev-packages/cloudflare-integration-tests/suites/tracing/vercelai/v6/wrangler.jsonc rename to dev-packages/cloudflare-integration-tests/suites/vite/diagnostics-channel/vercelai-6/wrangler.jsonc diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/package.json b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/package.json index a5d574038322..f557f00e891e 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/package.json +++ b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/package.json @@ -3,9 +3,9 @@ "version": "0.0.0", "private": true, "scripts": { - "deploy": "wrangler deploy", - "dev": "wrangler dev --var \"E2E_TEST_DSN:$E2E_TEST_DSN\" --log-level=$(test $CI && echo 'none' || echo 'log')", - "build": "wrangler deploy --dry-run", + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview --port 38787", "typecheck": "tsc --noEmit", "test:build": "pnpm install && pnpm build", "test:assert": "pnpm test:prod", @@ -17,11 +17,13 @@ "ai": "^7.0.0" }, "devDependencies": { + "@cloudflare/vite-plugin": "1.52.0", "@playwright/test": "~1.56.0", "@cloudflare/workers-types": "^4.20260426.0", "@sentry-internal/test-utils": "link:../../../test-utils", "typescript": "^5.5.2", - "wrangler": "4.86.0", + "vite": "7.3.2", + "wrangler": "^4.86.0", "ws": "^8.18.3" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/playwright.config.ts b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/playwright.config.ts index 59a70bd39838..3761c194df43 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/playwright.config.ts @@ -9,7 +9,7 @@ const APP_PORT = 38787; const config = getPlaywrightConfig( { - startCommand: `pnpm dev --port ${APP_PORT}`, + startCommand: `pnpm preview`, port: APP_PORT, }, { diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/vite.config.mts b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/vite.config.mts new file mode 100644 index 000000000000..005f4448f6cb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/vite.config.mts @@ -0,0 +1,7 @@ +import { cloudflare } from '@cloudflare/vite-plugin'; +import { sentryCloudflareVitePlugin } from '@sentry/cloudflare/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [cloudflare(), sentryCloudflareVitePlugin()], +}); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/wrangler.toml b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/wrangler.toml index 9e33c22a28ed..b416a58dd9ab 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/wrangler.toml +++ b/dev-packages/e2e-tests/test-applications/cloudflare-vercelai-v7/wrangler.toml @@ -3,3 +3,8 @@ name = "cloudflare-vercelai-v7-compat" main = "src/index.ts" compatibility_date = "2026-04-26" compatibility_flags = ["nodejs_compat"] + +# `vite preview` takes no `--var` flag, so the DSN is declared here and read from +# the process environment the Playwright web server inherits. +[secrets] +required = ["E2E_TEST_DSN"] diff --git a/packages/cloudflare/src/sdk.ts b/packages/cloudflare/src/sdk.ts index f96cbdc0626f..c07a5ec019a0 100644 --- a/packages/cloudflare/src/sdk.ts +++ b/packages/cloudflare/src/sdk.ts @@ -1,5 +1,4 @@ import type { Integration } from '@sentry/core'; -import { vercelAIIntegration } from './integrations/tracing/vercelai'; import { getBaseDefaultIntegrations, initWithDefaultIntegrations } from './baseSdk'; import type { CloudflareClient, CloudflareOptions } from './client'; @@ -11,11 +10,7 @@ import type { CloudflareClient, CloudflareOptions } from './client'; * `getBaseDefaultIntegrations`. */ export function getDefaultIntegrations(options: CloudflareOptions): Integration[] { - return [ - ...getBaseDefaultIntegrations(options), - // Subscribes to the `ai` SDK's native `node:diagnostics_channel` telemetry channel. - vercelAIIntegration(), - ]; + return getBaseDefaultIntegrations(options); } /** diff --git a/packages/server-utils/src/orchestrion/config/channel-integration-definitions.ts b/packages/server-utils/src/orchestrion/config/channel-integration-definitions.ts index df7400104578..446ce552a434 100644 --- a/packages/server-utils/src/orchestrion/config/channel-integration-definitions.ts +++ b/packages/server-utils/src/orchestrion/config/channel-integration-definitions.ts @@ -1,8 +1,8 @@ /** - * Build-time metadata mapping each instrumented package (orchestrion - * `module.name`) to the channel-subscriber integration that consumes the - * channels injected into it — by the `exportName` it is published under from - * `@sentry/server-utils/orchestrion`. + * Build-time metadata mapping each channel-subscriber integration — by the + * `exportName` it is published under from `@sentry/server-utils/orchestrion` — + * to the instrumented packages (orchestrion `module.name`) whose channels it + * consumes. * * Kept in a separate, factory-free module on purpose: the module-injected * transform (reachable from every orchestrion bundler plugin) reads this to @@ -20,6 +20,7 @@ export const CHANNEL_INTEGRATION_DEFINITIONS = [ { exportName: 'postgresJsIntegration', modules: ['postgres'] }, { exportName: 'mysqlIntegration', modules: ['mysql'] }, { exportName: 'mysql2Integration', modules: ['mysql2'] }, + { exportName: 'mongooseIntegration', modules: ['mongoose'] }, { exportName: 'genericPoolIntegration', modules: ['generic-pool'] }, { exportName: 'lruMemoizerIntegration', modules: ['lru-memoizer'] }, { exportName: 'openAIIntegration', modules: ['openai'] }, diff --git a/packages/server-utils/src/orchestrion/config/ioredis.ts b/packages/server-utils/src/orchestrion/config/ioredis.ts index edaa7f525985..c01465a7d8c4 100644 --- a/packages/server-utils/src/orchestrion/config/ioredis.ts +++ b/packages/server-utils/src/orchestrion/config/ioredis.ts @@ -1,7 +1,12 @@ import type { InstrumentationConfig } from '..'; import { getModuleNames } from './module-names'; +import { registrationOnly } from './registration-only'; export const ioredisConfig = [ + registrationOnly( + { name: 'ioredis', versionRange: '>=5.11.0', filePath: 'built/Redis.js' }, + { className: 'Redis', methodName: 'connect' }, + ), // ioredis `<5.11.0` (>=5.11.0 publishes its own `ioredis:*` diagnostics_channel) ...['lib/redis.js', 'built/redis.js', 'built/redis/index.js'].flatMap((filePath): InstrumentationConfig[] => [ { diff --git a/packages/server-utils/src/orchestrion/config/mongoose.ts b/packages/server-utils/src/orchestrion/config/mongoose.ts index 2a9c8c1ee4eb..d08cc1166424 100644 --- a/packages/server-utils/src/orchestrion/config/mongoose.ts +++ b/packages/server-utils/src/orchestrion/config/mongoose.ts @@ -1,5 +1,6 @@ import type { InstrumentationConfig } from '..'; import { getModuleNames } from './module-names'; +import { registrationOnly } from './registration-only'; // mongoose >= 9.7.0 publishes via its own `node:diagnostics_channel` tracing channels (handled by // `subscribeMongooseDiagnosticChannels`), so this transform is gated to `< 9.7.0` to avoid emitting @@ -37,6 +38,10 @@ const CONTEXT_CAPTURE_QUERY_METHODS = [ ] as const; export const mongooseConfig = [ + registrationOnly( + { name: 'mongoose', versionRange: '>=9.7.0', filePath: 'lib/query.js' }, + { expressionName: 'estimatedDocumentCount' }, + ), // Query execution // the span for most read/write operations. `op`, collection and model are // read off the `Query` at exec time. diff --git a/packages/server-utils/src/orchestrion/config/mysql2.ts b/packages/server-utils/src/orchestrion/config/mysql2.ts index 0dcb5e7067e4..d636f49ae060 100644 --- a/packages/server-utils/src/orchestrion/config/mysql2.ts +++ b/packages/server-utils/src/orchestrion/config/mysql2.ts @@ -1,5 +1,6 @@ import type { InstrumentationConfig } from '..'; import { getModuleNames } from './module-names'; +import { registrationOnly } from './registration-only'; // Ports `@opentelemetry/instrumentation-mysql2` (which patches `query`/`execute` on the connection // prototype) to orchestrion channel injection. @@ -21,6 +22,10 @@ import { getModuleNames } from './module-names'; // guard) — so `Auto` would crash streamed queries. `Callback` leaves that shape untouched (a rare, // row-streaming use that consumes the emitter's events), the tradeoff being it isn't traced. export const mysql2Config = [ + registrationOnly( + { name: 'mysql2', versionRange: '>=3.20.0', filePath: 'lib/base/connection.js' }, + { className: 'BaseConnection', methodName: 'pause' }, + ), { channelName: 'query', module: { name: 'mysql2', versionRange: '>=1.4.2 <3.11.5', filePath: 'lib/connection.js' }, diff --git a/packages/server-utils/src/orchestrion/config/redis.ts b/packages/server-utils/src/orchestrion/config/redis.ts index 7d5cc56839e4..8c8c42648ac9 100644 --- a/packages/server-utils/src/orchestrion/config/redis.ts +++ b/packages/server-utils/src/orchestrion/config/redis.ts @@ -1,7 +1,12 @@ import type { InstrumentationConfig } from '..'; import { getModuleNames } from './module-names'; +import { registrationOnly } from './registration-only'; export const redisConfig = [ + registrationOnly( + { name: '@redis/client', versionRange: '>=5.12.0', filePath: 'dist/lib/client/index.js' }, + { className: 'RedisClient', methodName: 'connect' }, + ), // redis `>=2.6.0 <4` (standalone `redis`). `internal_send_command` is an // anonymous prototype assignment (`expressionName`); it settles via the nested // `command_obj.callback`, so `kind: 'Sync'` and the subscriber wraps that callback. diff --git a/packages/server-utils/src/orchestrion/config/registration-only.ts b/packages/server-utils/src/orchestrion/config/registration-only.ts new file mode 100644 index 000000000000..dbdc42d811ea --- /dev/null +++ b/packages/server-utils/src/orchestrion/config/registration-only.ts @@ -0,0 +1,18 @@ +import type { InstrumentationConfig } from '..'; + +/** + * Build a registration-only config for one file of a library. + * + * `functionQuery` takes the same shapes as a normal config (`functionName`, + * `expressionName`, or `className` + `methodName`); the `kind` is fixed. + */ +export function registrationOnly( + module: { name: string; versionRange: string; filePath: string }, + functionQuery: { functionName?: string; expressionName?: string; className?: string; methodName?: string }, +): InstrumentationConfig { + return { + channelName: 'module-registration', + module, + functionQuery: { ...functionQuery, kind: 'Sync' }, + } as InstrumentationConfig; +} diff --git a/packages/server-utils/src/orchestrion/config/vercel-ai.ts b/packages/server-utils/src/orchestrion/config/vercel-ai.ts index 6aa86beba558..2025a17fce7d 100644 --- a/packages/server-utils/src/orchestrion/config/vercel-ai.ts +++ b/packages/server-utils/src/orchestrion/config/vercel-ai.ts @@ -1,7 +1,11 @@ import type { InstrumentationConfig } from '..'; import { getModuleNames } from './module-names'; +import { registrationOnly } from './registration-only'; export const vercelAiConfig = [ + ...['dist/index.js', 'dist/index.mjs'].map(filePath => + registrationOnly({ name: 'ai', versionRange: '>=7.0.0', filePath }, { functionName: 'embedMany' }), + ), // Vercel AI v6: mirror the v7 native `ai:telemetry` channel by injecting // channels into the top-level entry points. `resolveLanguageModel` is wrapped // not to span it, but so the subscriber can monkey-patch `doGenerate`/