Skip to content
Draft
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 @@ -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 },
Expand All @@ -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' },
}),
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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 })],
});
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const APP_PORT = 38787;

const config = getPlaywrightConfig(
{
startCommand: `pnpm dev --port ${APP_PORT}`,
startCommand: `pnpm preview`,
port: APP_PORT,
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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()],
});
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
7 changes: 1 addition & 6 deletions packages/cloudflare/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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'] },
Expand Down
5 changes: 5 additions & 0 deletions packages/server-utils/src/orchestrion/config/ioredis.ts
Original file line number Diff line number Diff line change
@@ -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[] => [
{
Expand Down
5 changes: 5 additions & 0 deletions packages/server-utils/src/orchestrion/config/mongoose.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions packages/server-utils/src/orchestrion/config/mysql2.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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' },
Expand Down
5 changes: 5 additions & 0 deletions packages/server-utils/src/orchestrion/config/redis.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
4 changes: 4 additions & 0 deletions packages/server-utils/src/orchestrion/config/vercel-ai.ts
Original file line number Diff line number Diff line change
@@ -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`/
Expand Down
Loading