Skip to content
Merged
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
@@ -0,0 +1,6 @@
import * as Sentry from '@sentry/bun';

Sentry.initWithoutDefaultIntegrations({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
tracesSampleRate: 1.0,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as Sentry from '@sentry/bun';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
tracesSampleRate: 1.0,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { spawnSync } from 'child_process';
import { join } from 'path';
import { describe, expect, it } from 'vitest';

function runScenario(file: string): string {
const res = spawnSync('bun', ['run', join(__dirname, file)], { cwd: __dirname, encoding: 'utf8' });
expect(res.status, `run failed:\nstderr:\n${res.stderr}\nstdout:\n${res.stdout}`).toBe(0);
return `${res.stdout}${res.stderr}`;
}

describe('no channel injection (Bun)', () => {
it('warns without `debug` when the app runs unbuilt with `bun run`', () => {
const output = runScenario('scenario.ts');

expect(output).toContain('No diagnostics-channel injection detected');
expect(output).toContain('@sentry/bun/plugin');
});

it('does not warn when no channel integration is installed', () => {
expect(runScenario('scenario-no-integrations.ts')).not.toContain('No diagnostics-channel injection detected');
});
});
12 changes: 11 additions & 1 deletion dev-packages/node-integration-tests/utils/runner/createRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,8 +665,18 @@ export function createRunner(...paths: string[]) {

if (process.env.DEBUG) log('stderr line', output);

// Bun runs these scenarios without the Bun build plugin, so the SDK warns that channel
// injection is missing. That warning is expected here and is not an error.
const unexpectedOutput =
process.env.RUNTIME === 'bun'
? output
.split('\n')
.filter(line => !line.includes('[Sentry] No diagnostics-channel injection detected.'))
.join('\n')
: output;

// Ignore deprecation warnings for this purpose
if (ensureNoErrorOutput && !`${output}`.includes('DeprecationWarning:')) {
if (ensureNoErrorOutput && unexpectedOutput.trim() && !unexpectedOutput.includes('DeprecationWarning:')) {
complete(new Error(`Expected no error output but got: '${output}'`));
}
});
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/utils/worldwide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ export type InternalGlobal = {
* Dedupes the one-time warning and short-circuits repeat calls.
*/
runtimeUnavailable?: boolean;
/**
* Set when an integration asks to subscribe to orchestrion-injected
* channels, so a missing injection is only reported when it loses data.
*/
channelIntegrations?: boolean;
/**
* Set once the Bun warning for missing channel injection has been shown.
* Lives on the marker so that duplicate SDK copies warn only once.
*/
warnedBunRunNotInjected?: boolean;
/**
* Module namespaces stashed by build-time provider imports, keyed by module
* name (e.g. `@mastra/observability`). A bundler plugin can splice a static
Expand Down
20 changes: 18 additions & 2 deletions packages/server-utils/src/orchestrion/detect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { debug, GLOBAL_OBJ } from '@sentry/core';
import { consoleSandbox, debug, GLOBAL_OBJ } from '@sentry/core';

/**
* The module names (e.g. `mysql`, `@hapi/hapi`) orchestrion has already injected
Expand Down Expand Up @@ -32,9 +32,25 @@ export function getOrchestrionInjectedModules(): string[] {
* is fine.
*/
export function detectOrchestrionSetup(): void {
const { runtime, bundler } = GLOBAL_OBJ.__SENTRY_ORCHESTRION__ ?? {};
const marker = GLOBAL_OBJ.__SENTRY_ORCHESTRION__ ?? {};
const { runtime, bundler, channelIntegrations } = marker;

if (!runtime && !bundler) {
if (channelIntegrations && typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined') {
if (!marker.warnedBunRunNotInjected) {
marker.warnedBunRunNotInjected = true;
consoleSandbox(() => {
// oxlint-disable-next-line no-console
console.warn(
'[Sentry] No diagnostics-channel injection detected. Channel-based integrations will not record ' +
'spans or capture errors, because Bun cannot instrument modules at runtime. Build your app with ' +
'`@sentry/bun/plugin` to enable them.',
);
});
}
return;
}

debug.warn(
'[Sentry] No diagnostics-channel injection detected. Channel-based integrations ' +
'will not record spans. Make sure the diagnostics channels are injected ' +
Expand Down
2 changes: 2 additions & 0 deletions packages/server-utils/src/orchestrion/instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export function invokeOrchestrionInstrumentation<Callback extends Instrumentatio
// they pass `false` so a missing binding never blocks their subscription.
{ requiresTracingChannelBinding = true }: { requiresTracingChannelBinding?: boolean } = {},
): void {
(GLOBAL_OBJ.__SENTRY_ORCHESTRION__ ??= {}).channelIntegrations = true;

const label = moduleNames.join(', ');

// `tracingChannel` is unavailable before Node 18.19; nothing to subscribe to.
Expand Down
66 changes: 64 additions & 2 deletions packages/server-utils/test/orchestrion/detect.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GLOBAL_OBJ } from '@sentry/core';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { getOrchestrionInjectedModules } from '../../src/orchestrion/detect';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { detectOrchestrionSetup, getOrchestrionInjectedModules } from '../../src/orchestrion/detect';

describe('getOrchestrionInjectedModules', () => {
beforeEach(() => {
Expand All @@ -25,3 +25,65 @@ describe('getOrchestrionInjectedModules', () => {
expect(getOrchestrionInjectedModules()).toEqual(['pg']);
});
});

describe('detectOrchestrionSetup', () => {
beforeEach(() => {
delete GLOBAL_OBJ.__SENTRY_ORCHESTRION__;
});

afterEach(() => {
delete GLOBAL_OBJ.__SENTRY_ORCHESTRION__;
vi.unstubAllGlobals();
vi.restoreAllMocks();
});

it('always warns on Bun when no injection is detected', () => {
vi.stubGlobal('Bun', {});
GLOBAL_OBJ.__SENTRY_ORCHESTRION__ = { channelIntegrations: true };
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

detectOrchestrionSetup();

expect(warn).toHaveBeenCalledTimes(1);
expect(warn).toHaveBeenCalledWith(expect.stringContaining('@sentry/bun/plugin'));
});

it('warns on Bun only once when called again, as on a repeated `init()`', () => {
vi.stubGlobal('Bun', {});
GLOBAL_OBJ.__SENTRY_ORCHESTRION__ = { channelIntegrations: true };
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

detectOrchestrionSetup();
detectOrchestrionSetup();

expect(warn).toHaveBeenCalledTimes(1);
});

it('does not warn on Bun when no channel integration is installed', () => {
vi.stubGlobal('Bun', {});
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

detectOrchestrionSetup();

expect(warn).not.toHaveBeenCalled();
});

it('does not warn on Bun when the bundler plugin ran', () => {
vi.stubGlobal('Bun', {});
GLOBAL_OBJ.__SENTRY_ORCHESTRION__ = { bundler: new Set(), channelIntegrations: true };
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

detectOrchestrionSetup();

expect(warn).not.toHaveBeenCalled();
});

it('does not warn outside Bun when debug is off', () => {
GLOBAL_OBJ.__SENTRY_ORCHESTRION__ = { channelIntegrations: true };
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

detectOrchestrionSetup();

expect(warn).not.toHaveBeenCalled();
});
});
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ describe('invokeOrchestrionInstrumentation', () => {
delete GLOBAL_OBJ.__SENTRY_ORCHESTRION__;
});

it('records on the marker that a channel integration is installed', () => {
invokeOrchestrionInstrumentation(makeClient() as never, ['mysql'], vi.fn(), []);

expect(GLOBAL_OBJ.__SENTRY_ORCHESTRION__?.channelIntegrations).toBe(true);
});

it('subscribes immediately when a module is already injected', () => {
GLOBAL_OBJ.__SENTRY_ORCHESTRION__ = { runtime: ['mysql'] };
const client = makeClient();
Expand Down
Loading