Skip to content
Open
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 @@ -61,13 +61,6 @@ Deno.test('amqplib instrumentation: included in default integrations (Deno 2.8.0
assert(names.includes('Amqplib'), `Amqplib should be in defaults, got ${names.join(', ')}`);
});

// Exercises the SDK path end-to-end: `init()` installs the AsyncLocalStorage
// context strategy and wires the default `amqplibChannelIntegration` (which
// subscribes to the channel), and we drive the `orchestrion:amqplib:publish`
// channel manually — the same events the orchestrion transform publishes around
// `Channel.prototype.publish` — so no live broker is needed. Asserting a nested
// producer `message` span proves the subscriber, the emitted attributes, AND the
// context-strategy wiring all work.
Deno.test('amqplib instrumentation: orchestrion:amqplib:publish channel produces a nested message span', async () => {
resetGlobals();
const sink = transactionSink();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,6 @@ Deno.test('koa instrumentation: included in default integrations (Deno 2.8.0+)',
assert(names.includes('Koa'), `Koa should be in defaults, got ${names.join(', ')}`);
});

// Exercises the SDK path end-to-end. Unlike the db integrations, koa's channel
// doesn't build a span directly: its `start` handler wraps the registered
// middleware (arg 0) in a span-creating proxy, and the span opens when that
// middleware later runs under an active span. So we publish `orchestrion:koa:use`
// with a middleware, then invoke the wrapped middleware inside a parent span —
// the same shape `app.use(fn)` then a request produces. Asserting a nested
// `middleware.koa` span proves the subscriber and context wiring work.
Deno.test('koa instrumentation: orchestrion:koa:use channel wraps middleware into a span', async () => {
resetGlobals();
const sink = transactionSink();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,6 @@ Deno.test('mongodb instrumentation: included in default integrations (Deno 2.8.0
assert(names.includes('Mongo'), `Mongo should be in defaults, got ${names.join(', ')}`);
});

// Exercises the SDK path end-to-end: `init()` installs the AsyncLocalStorage
// context strategy and wires the default `mongodbChannelIntegration` (which
// subscribes to the channel), and we drive the `orchestrion:mongodb:command`
// channel manually — the same events the orchestrion transform publishes around
// `Connection.prototype.command` — so no live database is needed. Asserting a
// nested `db` span proves the subscriber, the emitted attributes, AND the
// context-strategy wiring all work.
Deno.test('mongodb instrumentation: orchestrion:mongodb:command channel produces a nested db span', async () => {
resetGlobals();
const sink = transactionSink();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,6 @@ Deno.test('mongoose instrumentation: included in default integrations (Deno 2.8.
assert(names.includes('Mongoose'), `Mongoose should be in defaults, got ${names.join(', ')}`);
});

// Exercises the SDK path end-to-end: `init()` installs the AsyncLocalStorage
// context strategy and wires the default `mongooseChannelIntegration` (which
// subscribes to the channel), and we drive the `orchestrion:mongoose:model_save`
// channel manually — the same events the orchestrion transform publishes around
// `Model.prototype.save` — so no live database is needed. Asserting a nested
// `db` span proves the subscriber, the emitted attributes, AND the
// context-strategy wiring all work.
Deno.test('mongoose instrumentation: orchestrion:mongoose:model_save channel produces a nested db span', async () => {
resetGlobals();
const sink = transactionSink();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// <reference lib="deno.ns" />

import { tracingChannel } from 'node:diagnostics_channel';
import type { TransactionEvent } from '@sentry/core';
import type { DenoClient } from '@sentry/deno';
import { getCurrentScope, getGlobalScope, getIsolationScope, init, startSpan } from '@sentry/deno';
import { assert } from 'https://deno.land/std@0.212.0/assert/assert.ts';
import { assertExists } from 'https://deno.land/std@0.212.0/assert/assert_exists.ts';
import { assertEquals } from 'https://deno.land/std@0.212.0/assert/assert_equals.ts';

function resetGlobals(): void {
getCurrentScope().clear();
getCurrentScope().setClient(undefined);
getIsolationScope().clear();
getGlobalScope().clear();
}

/** See deno-redis.test.ts — same sink shape, deduped for clarity. */
function transactionSink(): {
beforeSendTransaction: (event: TransactionEvent) => null;
waitFor: (predicate: (event: TransactionEvent) => boolean) => Promise<TransactionEvent>;
} {
const transactions: TransactionEvent[] = [];
const waiters: { predicate: (e: TransactionEvent) => boolean; resolve: (e: TransactionEvent) => void }[] = [];
return {
beforeSendTransaction(event) {
transactions.push(event);
for (let i = waiters.length - 1; i >= 0; i--) {
const w = waiters[i]!;
if (w.predicate(event)) {
waiters.splice(i, 1);
w.resolve(event);
}
}
return null;
},
waitFor(predicate) {
const already = transactions.find(predicate);
if (already) return Promise.resolve(already);
return new Promise<TransactionEvent>(resolve => {
waiters.push({ predicate, resolve });
});
},
};
}

function withTimeout<T>(p: Promise<T>, ms: number, what: string): Promise<T> {
let timer: ReturnType<typeof setTimeout> | undefined;
const timeout = new Promise<T>((_, reject) => {
timer = setTimeout(() => reject(new Error(`Timed out waiting for ${what} after ${ms}ms`)), ms);
});
return Promise.race([p, timeout]).finally(() => {
if (timer !== undefined) clearTimeout(timer);
});
}

Deno.test('postgres.js instrumentation: included in default integrations (Deno 2.8.0+)', () => {
resetGlobals();
const client = init({ dsn: 'https://username@domain/123' }) as DenoClient;
const names = client.getOptions().integrations.map(i => i.name);
assert(names.includes('PostgresJs'), `PostgresJs should be in defaults, got ${names.join(', ')}`);
});

Deno.test('postgres.js instrumentation: orchestrion:postgres:handle channel produces a nested db span', async () => {
resetGlobals();
const sink = transactionSink();
init({
dsn: 'https://username@domain/123',
tracesSampleRate: 1,
beforeSendTransaction: sink.beforeSendTransaction,
});

const channel = tracingChannel('orchestrion:postgres:handle');

// `self` is the postgres.js `Query`; `strings` is its tagged-template SQL parts.
// The span ends when postgres.js calls `query.resolve`, which the subscriber wraps.
const query = {
strings: ['SELECT name FROM users'],
executed: false,
resolve: (..._args: unknown[]) => undefined,
reject: (..._args: unknown[]) => undefined,
};
const ctx = { self: query };

startSpan({ name: 'parent', op: 'test' }, () => {
// `start` creates the span and wraps `query.resolve`/`query.reject`.
channel.start.runStores(ctx, () => undefined);
// postgres.js signals completion by calling `resolve`; the wrapper ends the span.
query.resolve({ command: 'SELECT' });
});

const parent = await withTimeout(
sink.waitFor(t => t.transaction === 'parent'),
5000,
"'parent' transaction",
);

const pgSpan = parent.spans?.find(s => s.op === 'db');
assertExists(pgSpan, `expected a db child span, got ops: ${parent.spans?.map(s => s.op).join(', ')}`);
assertEquals(pgSpan!.description, 'SELECT name FROM users');
assertEquals(pgSpan!.data?.['db.system.name'], 'postgres');
assertEquals(pgSpan!.data?.['db.query.text'], 'SELECT name FROM users');
// Set by the resolve wrapper from the `command` passed to `query.resolve`.
assertEquals(pgSpan!.data?.['db.operation.name'], 'SELECT');
assertEquals(pgSpan!.data?.['sentry.origin'], 'auto.db.orchestrion.postgresjs');
Comment thread
cursor[bot] marked this conversation as resolved.
});
6 changes: 4 additions & 2 deletions packages/deno/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,9 @@ export type { DenoHttpIntegrationOptions } from './integrations/http';
export { denoRedisIntegration } from './integrations/redis';
export type { DenoRedisIntegrationOptions } from './integrations/redis';
// The orchestrion channel integrations, re-exported from `@sentry/server-utils`.
// The first six are in the default set; `dataloader` and `knex` are opt-in (add
// them to `integrations` to enable), matching Node.
// Most are in the default set; `dataloader` and `knex` are opt-in (add them to
// `integrations` to enable), matching Node. Re-export every one that `sdk.ts`
// adds to the defaults, so users who customize `defaultIntegrations` can re-add it.
export {
amqplibChannelIntegration,
dataloaderChannelIntegration,
Expand All @@ -123,6 +124,7 @@ export {
mongooseChannelIntegration,
mysqlChannelIntegration,
postgresChannelIntegration,
postgresJsChannelIntegration,
} from '@sentry/server-utils/orchestrion';
// Deprecated aliases kept for back-compat. Each forwards to the shared
// integration above, so its name is the shared name (e.g. `Mysql`), not the old
Expand Down
2 changes: 2 additions & 0 deletions packages/deno/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
mongooseChannelIntegration,
mysqlChannelIntegration,
postgresChannelIntegration,
postgresJsChannelIntegration,
Comment thread
cursor[bot] marked this conversation as resolved.
} from '@sentry/server-utils/orchestrion';
import { DenoClient } from './client';
import { breadcrumbsIntegration } from './integrations/breadcrumbs';
Expand Down Expand Up @@ -77,6 +78,7 @@ export function getDefaultIntegrations(_options: Options): Integration[] {
mongooseChannelIntegration(),
mysqlChannelIntegration(),
postgresChannelIntegration(),
postgresJsChannelIntegration(),
]
: []),
contextLinesIntegration(),
Expand Down
4 changes: 4 additions & 0 deletions packages/deno/test/__snapshots__/mod.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ snapshot[`captureException 1`] = `
"Mongoose",
"Mysql",
"Postgres",
"PostgresJs",
"ContextLines",
"NormalizePaths",
"GlobalHandlers",
Expand Down Expand Up @@ -202,6 +203,7 @@ snapshot[`captureMessage 1`] = `
"Mongoose",
"Mysql",
"Postgres",
"PostgresJs",
"ContextLines",
"NormalizePaths",
"GlobalHandlers",
Expand Down Expand Up @@ -290,6 +292,7 @@ snapshot[`captureMessage twice 1`] = `
"Mongoose",
"Mysql",
"Postgres",
"PostgresJs",
"ContextLines",
"NormalizePaths",
"GlobalHandlers",
Expand Down Expand Up @@ -385,6 +388,7 @@ snapshot[`captureMessage twice 2`] = `
"Mongoose",
"Mysql",
"Postgres",
"PostgresJs",
"ContextLines",
"NormalizePaths",
"GlobalHandlers",
Expand Down
Loading