-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
test(nextjs): Add zero-infra orchestrion instrumentations to e2e app #22507
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
Draft
chargome
wants to merge
2
commits into
develop
Choose a base branch
from
test/nextjs-orchestrion-zero-infra-instrumentations
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
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
15 changes: 15 additions & 0 deletions
15
dev-packages/e2e-tests/test-applications/nextjs-16-orchestrion/app/api/dataloader/route.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,15 @@ | ||
| import DataLoader from 'dataloader'; | ||
| import { NextResponse } from 'next/server'; | ||
|
|
||
| export const dynamic = 'force-dynamic'; | ||
|
|
||
| export async function GET() { | ||
| const loader = new DataLoader<string, number>(async keys => keys.map((_, idx) => idx), { | ||
| cache: false, | ||
| name: 'usersLoader', | ||
| }); | ||
|
|
||
| const user = await loader.load('user-1'); | ||
|
|
||
| return NextResponse.json({ user }); | ||
| } |
37 changes: 37 additions & 0 deletions
37
dev-packages/e2e-tests/test-applications/nextjs-16-orchestrion/app/api/generic-pool/route.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,37 @@ | ||
| import { createPool } from 'generic-pool'; | ||
| import { NextResponse } from 'next/server'; | ||
| import { Client } from 'pg'; | ||
|
|
||
| export const dynamic = 'force-dynamic'; | ||
|
|
||
| export async function GET() { | ||
| const pool = createPool( | ||
| { | ||
| create: async () => { | ||
| const client = new Client({ | ||
| host: 'localhost', | ||
| port: 5432, | ||
| user: 'postgres', | ||
| password: 'docker', | ||
| database: 'postgres', | ||
| }); | ||
| await client.connect(); | ||
| return client; | ||
| }, | ||
| destroy: async client => { | ||
| await client.end(); | ||
| }, | ||
| }, | ||
| { max: 2, min: 0 }, | ||
| ); | ||
|
|
||
| try { | ||
| const client = await pool.acquire(); | ||
| await client.query('SELECT 1 + 1 AS solution'); | ||
| await pool.release(client); | ||
| return NextResponse.json({ status: 'ok' }); | ||
| } finally { | ||
| await pool.drain(); | ||
| await pool.clear(); | ||
| } | ||
| } |
33 changes: 33 additions & 0 deletions
33
dev-packages/e2e-tests/test-applications/nextjs-16-orchestrion/app/api/knex/route.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,33 @@ | ||
| import knex from 'knex'; | ||
| import { NextResponse } from 'next/server'; | ||
|
|
||
| export const dynamic = 'force-dynamic'; | ||
|
|
||
| export async function GET() { | ||
| const db = knex({ | ||
| client: 'pg', | ||
| connection: { | ||
| host: 'localhost', | ||
| port: 5432, | ||
| user: 'postgres', | ||
| password: 'docker', | ||
| database: 'postgres', | ||
| }, | ||
| }); | ||
|
|
||
| try { | ||
| await db.schema.dropTableIfExists('knex_users'); | ||
| await db.schema.createTable('knex_users', table => { | ||
| table.increments('id').primary(); | ||
| table.text('name').notNullable(); | ||
| }); | ||
|
|
||
| await db('knex_users').insert({ name: 'bob' }); | ||
| await db('knex_users').select('*'); | ||
|
|
||
| return NextResponse.json({ status: 'ok' }); | ||
| } finally { | ||
| await db.schema.dropTableIfExists('knex_users'); | ||
| await db.destroy(); | ||
| } | ||
| } |
48 changes: 48 additions & 0 deletions
48
dev-packages/e2e-tests/test-applications/nextjs-16-orchestrion/app/api/lru-memoizer/route.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,48 @@ | ||
| import * as Sentry from '@sentry/nextjs'; | ||
| import memoizer from 'lru-memoizer'; | ||
| import { NextResponse } from 'next/server'; | ||
|
|
||
| export const dynamic = 'force-dynamic'; | ||
|
|
||
| // lru-memoizer's only job (from the SDK's perspective) is to bind the active async context onto the | ||
| // memoized callback, so it runs in its originating span's context whenever the load resolves. The | ||
| // integration creates no spans — we assert the context restore instead. | ||
| // | ||
| // `load` captures its callback without resolving. We register the memoized call INSIDE the | ||
| // `lru-memoizer-check` span (so orchestrion captures that span as the context to restore), but fire | ||
| // the load AFTER `startSpan` returns — i.e. outside the span's active context. That's essential: if | ||
| // we fired it inside the span, the callback would see the span through normal async propagation and | ||
| // the assertion would pass even with orchestrion's context restore broken. Firing it outside means | ||
| // only the restore can make the callback observe the span. Mirrors the node lru-memoizer test. | ||
| export async function GET() { | ||
| let memoizerLoadCallback: (() => void) | undefined; | ||
| const memoizedFn = memoizer({ | ||
| load: (_param: unknown, callback: () => void) => { | ||
| memoizerLoadCallback = callback; | ||
| }, | ||
| hash: () => 'key', | ||
| }); | ||
|
|
||
| // `startSpan` invokes its callback synchronously, so `memoizerLoadCallback` is captured by the time | ||
| // it returns. We don't await here — the callback only fires once the load below runs. | ||
| const spanFinished = Sentry.startSpan( | ||
| { name: 'lru-memoizer-check', op: 'run' }, | ||
| span => | ||
| new Promise<void>(resolve => { | ||
| memoizedFn({ foo: 'bar' }, () => { | ||
| span.setAttribute( | ||
| 'memoized.context_preserved', | ||
| Sentry.getActiveSpan()?.spanContext().spanId === span.spanContext().spanId, | ||
| ); | ||
| resolve(); | ||
| }); | ||
| }), | ||
| ); | ||
|
|
||
| // Fire the load outside the span's context, so the assertion above proves the context was restored. | ||
| memoizerLoadCallback?.(); | ||
|
|
||
| await spanFinished; | ||
|
|
||
| return NextResponse.json({ status: 'ok' }); | ||
| } | ||
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
126 changes: 126 additions & 0 deletions
126
...packages/e2e-tests/test-applications/nextjs-16-orchestrion/tests/instrumentations.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,126 @@ | ||
| import { expect, test } from '@playwright/test'; | ||
| import { waitForTransaction } from '@sentry-internal/test-utils'; | ||
|
|
||
| test('Instruments generic-pool automatically via orchestrion', async ({ baseURL }) => { | ||
| const transactionEventPromise = waitForTransaction('nextjs-16-orchestrion', transactionEvent => { | ||
| return ( | ||
| transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.transaction === 'GET /api/generic-pool' | ||
| ); | ||
| }); | ||
|
|
||
| await fetch(`${baseURL}/api/generic-pool`); | ||
|
|
||
| const transactionEvent = await transactionEventPromise; | ||
|
|
||
| const spans = transactionEvent.spans || []; | ||
|
|
||
| expect(spans).toContainEqual( | ||
| expect.objectContaining({ | ||
| description: 'generic-pool.acquire', | ||
| origin: 'auto.db.orchestrion.generic_pool', | ||
| status: 'ok', | ||
| data: expect.objectContaining({ | ||
| 'sentry.origin': 'auto.db.orchestrion.generic_pool', | ||
| }), | ||
| }), | ||
| ); | ||
| }); | ||
|
|
||
| test('Instruments dataloader automatically via orchestrion', async ({ baseURL }) => { | ||
| const transactionEventPromise = waitForTransaction('nextjs-16-orchestrion', transactionEvent => { | ||
| return ( | ||
| transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.transaction === 'GET /api/dataloader' | ||
| ); | ||
| }); | ||
|
|
||
| await fetch(`${baseURL}/api/dataloader`); | ||
|
|
||
| const transactionEvent = await transactionEventPromise; | ||
|
|
||
| const spans = transactionEvent.spans || []; | ||
|
|
||
| const loadSpan = spans.find(span => span.description === 'dataloader.load usersLoader'); | ||
| expect(loadSpan).toBeDefined(); | ||
| expect(loadSpan?.op).toBe('cache.get'); | ||
| expect(loadSpan?.origin).toBe('auto.db.orchestrion.dataloader'); | ||
| expect(loadSpan?.status).toBe('ok'); | ||
| expect(loadSpan?.data?.['cache.key']).toEqual(['user-1']); | ||
|
|
||
| // The batch span opens on the deferred dispatch tick and links back to the load span. | ||
| const batchSpan = spans.find(span => span.description === 'dataloader.batch usersLoader'); | ||
| expect(batchSpan).toBeDefined(); | ||
| expect(batchSpan?.op).toBe('cache.get'); | ||
| expect(batchSpan?.origin).toBe('auto.db.orchestrion.dataloader'); | ||
| expect(batchSpan?.status).toBe('ok'); | ||
| }); | ||
|
|
||
| test('Instruments knex automatically via orchestrion', async ({ baseURL }) => { | ||
| const transactionEventPromise = waitForTransaction('nextjs-16-orchestrion', transactionEvent => { | ||
| return transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.transaction === 'GET /api/knex'; | ||
| }); | ||
|
|
||
| await fetch(`${baseURL}/api/knex`); | ||
|
|
||
| const transactionEvent = await transactionEventPromise; | ||
|
|
||
| const spans = transactionEvent.spans || []; | ||
|
|
||
| expect(spans).toContainEqual( | ||
| expect.objectContaining({ | ||
| op: 'db', | ||
| origin: 'auto.db.orchestrion.knex', | ||
| status: 'ok', | ||
| description: 'insert into "knex_users" ("name") values (?)', | ||
| data: expect.objectContaining({ | ||
| 'db.system': 'postgresql', | ||
| 'db.name': 'postgres', | ||
| 'sentry.origin': 'auto.db.orchestrion.knex', | ||
| 'sentry.op': 'db', | ||
| 'net.peer.name': 'localhost', | ||
| 'net.peer.port': 5432, | ||
| }), | ||
| }), | ||
| ); | ||
| expect(spans).toContainEqual( | ||
| expect.objectContaining({ | ||
| op: 'db', | ||
| origin: 'auto.db.orchestrion.knex', | ||
| status: 'ok', | ||
| description: 'select * from "knex_users"', | ||
| data: expect.objectContaining({ | ||
| 'db.system': 'postgresql', | ||
| 'db.operation': 'select', | ||
| 'db.sql.table': 'knex_users', | ||
| 'db.statement': 'select * from "knex_users"', | ||
| 'sentry.origin': 'auto.db.orchestrion.knex', | ||
| 'sentry.op': 'db', | ||
| }), | ||
| }), | ||
| ); | ||
| }); | ||
|
|
||
| // lru-memoizer's channel integration creates no spans — its only job is to restore the caller's async | ||
| // context onto the memoized callback. The route wraps the check in a `lru-memoizer-check` span and | ||
| // records whether the callback ran in that span's context, so we assert the attribute on that span. | ||
| test('Preserves async context through lru-memoizer via orchestrion', async ({ baseURL }) => { | ||
| const transactionEventPromise = waitForTransaction('nextjs-16-orchestrion', transactionEvent => { | ||
| return ( | ||
| transactionEvent.contexts?.trace?.op === 'http.server' && transactionEvent.transaction === 'GET /api/lru-memoizer' | ||
| ); | ||
| }); | ||
|
|
||
| await fetch(`${baseURL}/api/lru-memoizer`); | ||
|
|
||
| const transactionEvent = await transactionEventPromise; | ||
|
|
||
| const spans = transactionEvent.spans || []; | ||
|
|
||
| expect(spans).toContainEqual( | ||
| expect.objectContaining({ | ||
| description: 'lru-memoizer-check', | ||
| data: expect.objectContaining({ | ||
| 'memoized.context_preserved': true, | ||
| }), | ||
| }), | ||
| ); | ||
| }); |
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.
Uh oh!
There was an error while loading. Please reload this page.