-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(cloudflare): Add @sentry/cloudflare/vite orchestrion plugin #21967
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
2 changes: 2 additions & 0 deletions
2
dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/.gitignore
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| dist | ||
| .wrangler |
18 changes: 18 additions & 0 deletions
18
dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/docker-compose.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| services: | ||
| db: | ||
| image: mysql:8.0 | ||
| restart: always | ||
| container_name: e2e-tests-cloudflare-orchestrion-mysql | ||
| # The `mysql` 2.x driver doesn't speak MySQL 8's default | ||
| # `caching_sha2_password` auth, so force the legacy plugin. | ||
| command: ['--default-authentication-plugin=mysql_native_password'] | ||
| ports: | ||
| - '3306:3306' | ||
| environment: | ||
| MYSQL_ROOT_PASSWORD: password | ||
| healthcheck: | ||
| test: ['CMD-SHELL', 'mysqladmin ping -h 127.0.0.1 -uroot -ppassword'] | ||
| interval: 2s | ||
| timeout: 3s | ||
| retries: 30 | ||
| start_period: 10s |
14 changes: 14 additions & 0 deletions
14
dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/global-setup.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { execSync } from 'child_process'; | ||
| import { dirname } from 'path'; | ||
| import { fileURLToPath } from 'url'; | ||
|
|
||
| const __dirname = dirname(fileURLToPath(import.meta.url)); | ||
|
|
||
| export default async function globalSetup() { | ||
| // Start MySQL via Docker Compose. `--wait` blocks until the healthcheck in | ||
| // docker-compose.yml passes, so the worker can connect on the first request. | ||
| execSync('docker compose up -d --wait', { | ||
| cwd: __dirname, | ||
| stdio: 'inherit', | ||
| }); | ||
| } |
12 changes: 12 additions & 0 deletions
12
dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/global-teardown.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { execSync } from 'child_process'; | ||
| import { dirname } from 'path'; | ||
| import { fileURLToPath } from 'url'; | ||
|
|
||
| const __dirname = dirname(fileURLToPath(import.meta.url)); | ||
|
|
||
| export default async function globalTeardown() { | ||
| execSync('docker compose down --volumes', { | ||
| cwd: __dirname, | ||
| stdio: 'inherit', | ||
| }); | ||
| } |
34 changes: 34 additions & 0 deletions
34
dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/package.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| { | ||
| "name": "cloudflare-orchestrion-mysql", | ||
| "version": "0.0.0", | ||
| "private": true, | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite dev", | ||
| "build": "vite build", | ||
| "preview": "wrangler dev --var \"E2E_TEST_DSN:$E2E_TEST_DSN\" --log-level=$(test $CI && echo 'none' || echo 'log')", | ||
| "test": "playwright test", | ||
| "typecheck": "tsc --noEmit", | ||
| "test:build": "pnpm install && pnpm build", | ||
| "test:assert": "pnpm typecheck && pnpm test" | ||
| }, | ||
| "dependencies": { | ||
| "@sentry/cloudflare": "file:../../packed/sentry-cloudflare-packed.tgz", | ||
| "mysql": "2.18.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/vite-plugin": "^1.35.0", | ||
| "@playwright/test": "~1.56.0", | ||
| "@cloudflare/workers-types": "^4.20260629.0", | ||
| "@sentry-internal/test-utils": "link:../../../test-utils", | ||
| "@types/node": "^24.12.4", | ||
| "typescript": "^5.5.2", | ||
| "vite": "7.3.2", | ||
| "wrangler": "^4.61.0", | ||
| "ws": "^8.18.3" | ||
| }, | ||
| "volta": { | ||
| "node": "24.15.0", | ||
| "extends": "../../package.json" | ||
| } | ||
| } |
22 changes: 22 additions & 0 deletions
22
dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/playwright.config.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { getPlaywrightConfig } from '@sentry-internal/test-utils'; | ||
|
|
||
| // `vite build` (where the Sentry plugin's orchestrion transform runs) produces | ||
| // the worker; `pnpm preview` (`wrangler dev`, following the vite plugin's | ||
| // `.wrangler/deploy` redirect to the built output) serves it. `globalSetup` | ||
| // spins up the MySQL container the worker connects to. | ||
| const config = getPlaywrightConfig( | ||
| { | ||
| startCommand: 'pnpm preview', | ||
| port: 8787, | ||
| }, | ||
| { | ||
| workers: '100%', | ||
| retries: 0, | ||
| }, | ||
| ); | ||
|
|
||
| export default { | ||
| ...config, | ||
| globalSetup: './global-setup.mjs', | ||
| globalTeardown: './global-teardown.mjs', | ||
| }; |
3 changes: 3 additions & 0 deletions
3
dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/src/env.d.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| interface Env { | ||
| E2E_TEST_DSN: string; | ||
| } |
73 changes: 73 additions & 0 deletions
73
dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/src/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import * as Sentry from '@sentry/cloudflare'; | ||
| // @ts-ignore -- `mysql` ships no type declarations; only needed at runtime. | ||
| import mysql from 'mysql'; | ||
|
|
||
| // The `@sentry/cloudflare/vite` plugin's orchestrion transform injects the | ||
| // `orchestrion:mysql:query` diagnostics channel into the bundled `mysql` | ||
| // package at build time. The SDK detects the injection and subscribes to the | ||
| // channel, so the queries below produce `db` spans with no OTel require-hook — | ||
| // which wouldn't work in workerd anyway. | ||
|
|
||
| interface Connection { | ||
| query(sql: string, cb: (err: unknown, results?: unknown) => void): void; | ||
| end(cb?: (err: unknown) => void): void; | ||
| on(event: string, cb: (err: unknown) => void): void; | ||
| } | ||
|
|
||
| interface MysqlModule { | ||
| createConnection(opts: { host: string; port: number; user: string; password: string }): Connection; | ||
| } | ||
|
|
||
| export default Sentry.withSentry( | ||
| (env: Env) => ({ | ||
| dsn: env.E2E_TEST_DSN, | ||
| environment: 'qa', | ||
| tunnel: 'http://localhost:3031/', | ||
| tracesSampleRate: 1.0, | ||
| transportOptions: { | ||
| bufferSize: 1000, | ||
| }, | ||
| }), | ||
| { | ||
| async fetch(request: Request): Promise<Response> { | ||
| const url = new URL(request.url); | ||
|
|
||
| // Runs two queries, the second NESTED inside the first's callback. mysql | ||
| // dispatches that callback from its socket data handler (a fresh async | ||
| // context), so the nested query's span only lands on this request's | ||
| // http.server transaction if the channel subscriber restored the parent | ||
| // span across that async boundary. | ||
| if (url.pathname === '/test-mysql') { | ||
| // The connection is created inside the handler: workerd forbids I/O in | ||
| // global scope, and mysql opens its socket lazily on the first query. | ||
| const connection = (mysql as MysqlModule).createConnection({ | ||
| host: '127.0.0.1', | ||
| port: 3306, | ||
| user: 'root', | ||
| password: 'password', | ||
| }); | ||
|
|
||
| // Swallow connection-level errors so a socket hiccup doesn't become an | ||
| // uncaught exception that fails the request unrelated to the spans. | ||
| connection.on('error', () => { | ||
| // no-op | ||
| }); | ||
|
|
||
| await new Promise<void>((resolve, reject) => { | ||
| connection.query('SELECT 1 + 1 AS solution', (err: unknown) => { | ||
| if (err) return reject(err); | ||
| connection.query('SELECT NOW()', (err2: unknown) => { | ||
| connection.end(); | ||
| if (err2) return reject(err2); | ||
| resolve(); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| return Response.json({ status: 'ok' }); | ||
| } | ||
|
|
||
| return new Response('Not found', { status: 404 }); | ||
| }, | ||
| } satisfies ExportedHandler<Env>, | ||
| ); |
6 changes: 6 additions & 0 deletions
6
dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/start-event-proxy.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { startEventProxyServer } from '@sentry-internal/test-utils'; | ||
|
|
||
| startEventProxyServer({ | ||
| port: 3031, | ||
| proxyServerName: 'cloudflare-orchestrion-mysql', | ||
| }); |
57 changes: 57 additions & 0 deletions
57
dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/tests/mysql.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import { expect, test } from '@playwright/test'; | ||
| import { waitForTransaction } from '@sentry-internal/test-utils'; | ||
|
|
||
| test('a real mysql query emits a db span with orchestrion-channel attributes', async ({ baseURL }) => { | ||
| // Each incoming request gets a Sentry http.server transaction; the mysql | ||
| // queries run inside it, so their db spans attach to it. The | ||
| // `orchestrion:mysql:query` channel was injected into the bundled `mysql` | ||
| // package at build time by `@sentry/cloudflare/vite`, and the Cloudflare SDK | ||
| // subscribes to it once it detects the injection. | ||
| const transactionPromise = waitForTransaction('cloudflare-orchestrion-mysql', event => { | ||
| return ( | ||
| event?.contexts?.trace?.op === 'http.server' && | ||
| (event.request?.url ?? '').includes('/test-mysql') && | ||
| (event.spans?.some(span => span.op === 'db') ?? false) | ||
| ); | ||
| }); | ||
|
|
||
| const res = await fetch(`${baseURL}/test-mysql`); | ||
| expect(res.status).toBe(200); | ||
| await res.json(); | ||
|
|
||
| const transaction = await transactionPromise; | ||
| const dbSpans = transaction.spans!.filter(span => span.op === 'db'); | ||
|
|
||
| const firstQuery = dbSpans.find(span => span.description === 'SELECT 1 + 1 AS solution'); | ||
| expect(firstQuery).toBeDefined(); | ||
| expect(firstQuery!.data?.['sentry.origin']).toBe('auto.db.orchestrion.mysql'); | ||
| expect(firstQuery!.data?.['db.system']).toBe('mysql'); | ||
| expect(firstQuery!.data?.['db.statement']).toBe('SELECT 1 + 1 AS solution'); | ||
| expect(firstQuery!.data?.['net.peer.name']).toBe('127.0.0.1'); | ||
| expect(firstQuery!.data?.['net.peer.port']).toBe(3306); | ||
| expect(firstQuery!.data?.['db.user']).toBe('root'); | ||
| }); | ||
|
|
||
| test('a nested query lands on the same transaction (async context restored)', async ({ baseURL }) => { | ||
| // The second query runs inside the first query's callback — i.e. across | ||
| // mysql's async socket-callback dispatch. Both spans appearing on the SAME | ||
| // http.server transaction proves the channel subscriber restored the parent | ||
| // span across that async boundary (otherwise the nested query would start its | ||
| // own trace and never join this transaction). | ||
| const transactionPromise = waitForTransaction('cloudflare-orchestrion-mysql', event => { | ||
| return ( | ||
| event?.contexts?.trace?.op === 'http.server' && | ||
| (event.request?.url ?? '').includes('/test-mysql') && | ||
| (event.spans?.filter(span => span.op === 'db').length ?? 0) >= 2 | ||
| ); | ||
| }); | ||
|
|
||
| const res = await fetch(`${baseURL}/test-mysql`); | ||
| expect(res.status).toBe(200); | ||
| await res.json(); | ||
|
|
||
| const transaction = await transactionPromise; | ||
| const descriptions = transaction.spans!.filter(span => span.op === 'db').map(span => span.description); | ||
| expect(descriptions).toContain('SELECT 1 + 1 AS solution'); | ||
| expect(descriptions).toContain('SELECT NOW()'); | ||
| }); |
16 changes: 16 additions & 0 deletions
16
dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/tsconfig.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "es2021", | ||
| "lib": ["es2021"], | ||
| "module": "es2022", | ||
| "moduleResolution": "bundler", | ||
| "types": ["@cloudflare/workers-types", "node"], | ||
| "skipLibCheck": true, | ||
| "noEmit": true, | ||
| "isolatedModules": true, | ||
| "allowSyntheticDefaultImports": true, | ||
| "forceConsistentCasingInFileNames": true, | ||
| "strict": true | ||
| }, | ||
| "include": ["src/**/*"] | ||
| } |
14 changes: 14 additions & 0 deletions
14
dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/vite.config.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { cloudflare } from '@cloudflare/vite-plugin'; | ||
| import { sentryCloudflareVitePlugin } from '@sentry/cloudflare/vite'; | ||
| import { defineConfig } from 'vite'; | ||
|
|
||
| export default defineConfig({ | ||
| plugins: [ | ||
| cloudflare(), | ||
| sentryCloudflareVitePlugin({ | ||
| _experimental: { | ||
| useDiagnosticsChannelInjection: true, | ||
| }, | ||
| }), | ||
| ], | ||
| }); | ||
7 changes: 7 additions & 0 deletions
7
dev-packages/e2e-tests/test-applications/cloudflare-orchestrion-mysql/wrangler.jsonc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "$schema": "node_modules/wrangler/config-schema.json", | ||
| "name": "cloudflare-orchestrion-mysql", | ||
| "main": "src/index.ts", | ||
| "compatibility_date": "2026-06-29", | ||
| "compatibility_flags": ["nodejs_compat"], | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,6 +46,10 @@ | |
| "types": "./build/types/nodejs_compat/index.d.ts", | ||
| "default": "./build/cjs/nodejs_compat/index.js" | ||
| } | ||
| }, | ||
| "./vite": { | ||
| "types": "./build/types/vite/index.d.ts", | ||
| "import": "./build/esm/vite/index.js" | ||
|
Comment on lines
+51
to
+52
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't you need the cjs export here as well? Other exports have it 👀 Edit: I just noticed the comment above that it's only ESM. But this PR could fix that: #22286 |
||
| } | ||
| }, | ||
| "typesVersions": { | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the only option that would be needed to enable this?
Asking because usually, there's still the
Sentry.experimentalUseDiagnosticsChannelInjection();beforeinit(while we're on v10).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
experimentalUseDiagnosticsChannelInjectionwouldn't have an effect right now, because the Cloudflare SDK is using the core client and not the one from the Node SDK.That would only change if we move this entire snippet over to Cloudflare directly:
sentry-javascript/packages/node/src/sdk/index.ts
Line 92 in fab3366