Skip to content

Commit c9716eb

Browse files
authored
feat(server-utils): Capture and log orchestrion stats (#22269)
1 parent 207528e commit c9716eb

9 files changed

Lines changed: 77 additions & 67 deletions

File tree

dev-packages/deno-integration-tests/suites/orchestrion-mysql/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ Deno.test('@sentry/deno/import: transforms mysql so it publishes the orchestrion
9292
// ...with the real SQL forwarded through the channel context.
9393
assert(line.includes('statement=SELECT 1 AS solution'), `expected forwarded SQL, got: ${line}`);
9494
// The runtime hook set its detection marker at boot.
95-
assert(line.includes('"runtime":true'), `expected runtime marker, got: ${line}`);
95+
assert(line.includes('"runtime":["mysql"]'), `expected runtime marker, got: ${line}`);
9696
});
9797

9898
// Exercises the SDK path end-to-end: `init()` wires `denoMysqlIntegration`

dev-packages/deno-integration-tests/suites/orchestrion-postgres/test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ Deno.test('@sentry/deno/import: transforms pg so it publishes the orchestrion ch
9292
// ...with the real SQL forwarded through the channel context.
9393
assert(line.includes('statement=SELECT 1 AS solution'), `expected forwarded SQL, got: ${line}`);
9494
// The runtime hook set its detection marker at boot.
95-
assert(line.includes('"runtime":true'), `expected runtime marker, got: ${line}`);
95+
assert(line.includes('"runtime":["pg","pg-pool"]'), `expected runtime marker, got: ${line}`);
9696
});
9797

9898
// Exercises the SDK path end-to-end: `init()` wires `denoPostgresIntegration`

packages/bun/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"access": "public"
5050
},
5151
"dependencies": {
52-
"@apm-js-collab/code-transformer-bundler-plugins": "^0.6.0",
52+
"@apm-js-collab/code-transformer-bundler-plugins": "^0.6.1",
5353
"@sentry/core": "10.65.0",
5454
"@sentry/node": "10.65.0",
5555
"@sentry/server-utils": "10.65.0"

packages/core/src/utils/worldwide.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@ export type InternalGlobal = {
5555
_sentryModuleMetadata?: Record<string, any>;
5656
_sentryEsmLoaderHookRegistered?: boolean;
5757
_sentryWrappedDepth?: number;
58+
/**
59+
* Orchestrion bundler and runtime detection.
60+
*/
61+
__SENTRY_ORCHESTRION__?: {
62+
/** Empty array signifies runtime hooked */
63+
runtime?: string[];
64+
/** Empty array signifies bundler plugin ran */
65+
bundler?: string[];
66+
};
5867
} & Carrier;
5968

6069
/** Get's the global object for the current JavaScript runtime */

packages/server-utils/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,9 @@
116116
"access": "public"
117117
},
118118
"dependencies": {
119-
"@apm-js-collab/code-transformer-bundler-plugins": "^0.6.0",
119+
"@apm-js-collab/code-transformer-bundler-plugins": "^0.6.1",
120120
"@apm-js-collab/code-transformer": "^0.18.0",
121-
"@apm-js-collab/tracing-hooks": "^0.12.0",
121+
"@apm-js-collab/tracing-hooks": "^0.13.0",
122122
"@sentry/conventions": "^0.16.0",
123123
"@sentry/core": "10.65.0"
124124
},

packages/server-utils/src/orchestrion/bundler/options.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ export type PluginOptions = {
1313
* The `@apm-js-collab/code-transformer-bundler-plugins` options shared by every
1414
* orchestrion bundler plugin.
1515
*
16-
* `injectDiagnostics` sets `globalThis.__SENTRY_ORCHESTRION__.bundler = true` at
16+
* `injectDiagnostics` sets `globalThis.__SENTRY_ORCHESTRION__.bundler = ["mysql"]` at
1717
* app boot so the `_experimentalSetupOrchestrion()` detector can confirm the
1818
* bundler path ran (rather than relying on a build-time flag that wouldn't be
1919
* visible to the runtime).
2020
*/
2121
export function orchestrionTransformOptions(options: PluginOptions): Parameters<typeof codeTransformer>[0] {
2222
return {
2323
instrumentations: [...SENTRY_INSTRUMENTATIONS, ...(options.instrumentations || [])],
24-
injectDiagnostics: () => {
25-
return '(globalThis.__SENTRY_ORCHESTRION__=globalThis.__SENTRY_ORCHESTRION__||{}).bundler=true;';
24+
injectDiagnostics: (diag: { transformedModules: string[]; failedModules: string[] }) => {
25+
return `(globalThis.__SENTRY_ORCHESTRION__=globalThis.__SENTRY_ORCHESTRION__||{}).bundler=${JSON.stringify(diag.transformedModules)};`;
2626
},
2727
};
2828
}
Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import { debug } from '@sentry/core';
2-
import { DEBUG_BUILD } from '../debug-build';
3-
4-
declare global {
5-
// eslint-disable-next-line no-var
6-
var __SENTRY_ORCHESTRION__: { runtime?: boolean; bundler?: boolean } | undefined;
7-
}
1+
import { debug, GLOBAL_OBJ } from '@sentry/core';
82

93
/**
104
* Whether orchestrion has injected the diagnostics channels into this process,
@@ -16,40 +10,43 @@ declare global {
1610
* will ever publish to those channels.
1711
*/
1812
export function isOrchestrionInjected(): boolean {
19-
const marker = globalThis.__SENTRY_ORCHESTRION__;
20-
return !!(marker?.runtime || marker?.bundler);
13+
return !!GLOBAL_OBJ.__SENTRY_ORCHESTRION__;
2114
}
2215

2316
/**
2417
* Verifies that the diagnostics channels have been injected either by the
2518
* runtime `--import` hook (or init-time registration), a bundler plugin, or
26-
* both, and warns if not.
19+
* both, and warns if not. When at least one injector is active, logs for each
20+
* mechanism whether it hooked (a defined array, even empty, means it did) and
21+
* which libraries it injected.
2722
*
2823
* Both injectors being active at once is fine: they operate on disjoint module
2924
* sets (a module is either loaded through Node's loader and transformed by the
3025
* runtime hook, or inlined by the bundler and transformed by the plugin), so
3126
* a single module can't be double-wrapped. A hybrid setup, with some deps
3227
* external and runtime-instrumented, others bundled and plugin-instrumented,
3328
* is fine.
34-
*
35-
* Note: intentionally does NOT warn in production, only in debug builds,
36-
* because production warnings are reserved for truly critical issues.
3729
*/
3830
export function detectOrchestrionSetup(): void {
39-
if (!DEBUG_BUILD) return;
31+
const { runtime, bundler } = GLOBAL_OBJ.__SENTRY_ORCHESTRION__ ?? {};
4032

41-
const marker = globalThis.__SENTRY_ORCHESTRION__;
42-
const runtime = !!marker?.runtime;
43-
const bundler = !!marker?.bundler;
44-
45-
DEBUG_BUILD && debug.log(`[orchestrion] detect: runtime=${runtime} bundler=${bundler}`);
46-
47-
if (!isOrchestrionInjected()) {
48-
DEBUG_BUILD &&
49-
debug.warn(
50-
'[Sentry] No diagnostics-channel injection detected. Channel-based integrations ' +
51-
'(mysql, …) will not record spans. Make sure the diagnostics channels are injected ' +
52-
'via the runtime `--import` hook or a bundler plugin before the instrumented modules load.',
53-
);
33+
if (!runtime && !bundler) {
34+
debug.warn(
35+
'[Sentry] No diagnostics-channel injection detected. Channel-based integrations ' +
36+
'will not record spans. Make sure the diagnostics channels are injected ' +
37+
'via the runtime `--import` hook or a bundler plugin before the instrumented modules load.',
38+
);
39+
return;
5440
}
41+
42+
debug.log(
43+
runtime
44+
? `[Sentry] Runtime hook registered, injected libraries=${JSON.stringify(runtime)}`
45+
: '[Sentry] Runtime hook not registered',
46+
);
47+
debug.log(
48+
bundler
49+
? `[Sentry] Bundler plugin ran, injected libraries=${JSON.stringify(bundler)}`
50+
: '[Sentry] Bundler plugin did not run',
51+
);
5552
}

packages/server-utils/src/orchestrion/runtime/register.ts

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
1-
import { debug } from '@sentry/core';
1+
import { debug, GLOBAL_OBJ } from '@sentry/core';
22
import { createRequire } from 'node:module';
33
import * as Module from 'node:module';
44
import { pathToFileURL } from 'node:url';
55
import { DEBUG_BUILD } from '../../debug-build';
66
import { SENTRY_INSTRUMENTATIONS } from '../config';
7+
import type { InstrumentationConfig } from '@apm-js-collab/code-transformer';
8+
import type { register } from 'node:module';
9+
10+
type TracingHooksSync = {
11+
initialize: (opts: { instrumentations: InstrumentationConfig[] }) => void;
12+
resolve: Function;
13+
load: Function;
14+
setDiagnosticsHook: (callback: (event: { url: string; moduleName: string; error: Error }) => void) => void;
15+
};
16+
17+
type NodeModule = {
18+
registerHooks?: (options: unknown) => { deregister: () => void };
19+
register?: typeof register;
20+
};
721

822
export interface RegisterDiagnosticsChannelInjectionOptions {
923
/**
@@ -17,11 +31,6 @@ export interface RegisterDiagnosticsChannelInjectionOptions {
1731
tracingHooksDir?: string;
1832
}
1933

20-
declare global {
21-
// eslint-disable-next-line no-var
22-
var __SENTRY_ORCHESTRION__: { runtime?: boolean; bundler?: boolean } | undefined;
23-
}
24-
2534
/** `Module.registerHooks` only became stable in Node 24.13 / 25.1 and Deno 2.8. */
2635
function hasStableSyncModuleHooks(denoVersionString: string | undefined): boolean {
2736
const parseVersion = (v: string): number[] => v.split('.').map(n => parseInt(n, 10));
@@ -47,15 +56,9 @@ function hasStableSyncModuleHooks(denoVersionString: string | undefined): boolea
4756
*
4857
* Libraries imported *after* this call publish the `tracingChannel` events that
4958
* the channel-based integrations subscribe to.
50-
*
51-
* Idempotent via `globalThis.__SENTRY_ORCHESTRION__` — a no-op if the runtime
52-
* `--import` hook or a bundler plugin already injected the channels.
5359
*/
5460
export function registerDiagnosticsChannelInjection(options?: RegisterDiagnosticsChannelInjectionOptions): void {
55-
const g = (globalThis.__SENTRY_ORCHESTRION__ ??= {});
56-
57-
// Already injected (runtime --import hook or bundler plugin) — nothing to do.
58-
if (g.runtime) {
61+
if (GLOBAL_OBJ?.__SENTRY_ORCHESTRION__?.runtime) {
5962
return;
6063
}
6164

@@ -89,10 +92,7 @@ export function registerDiagnosticsChannelInjection(options?: RegisterDiagnostic
8992

9093
// `Module.registerHooks` / `Module.register` are newer than the @types/node
9194
// we build against, hence the cast.
92-
const mod = Module as unknown as {
93-
registerHooks?: (hooks: unknown) => void;
94-
register?: (specifier: string, options: unknown) => void;
95-
};
95+
const mod = Module as NodeModule;
9696

9797
// runs both at `--import` time and (synchronously) inside `Sentry.init()`,
9898
// so an unguarded throw would either abort startup or make `init()` throw.
@@ -105,15 +105,18 @@ export function registerDiagnosticsChannelInjection(options?: RegisterDiagnostic
105105
// We require() the module here so that we can synchronously load it,
106106
// including from a CommonJS Sentry build, without bundlers pulling in.
107107
// All versions in stableSyncHooks support this.
108-
const { initialize, resolve, load } = (
108+
const { initialize, resolve, load, setDiagnosticsHook } = (
109109
requireFromHooksDir
110110
? requireFromHooksDir(`${tracingHooksDir}/hook-sync.mjs`)
111111
: nodeRequire('@apm-js-collab/tracing-hooks/hook-sync.mjs')
112-
) as {
113-
initialize: (opts: { instrumentations: unknown }) => void;
114-
resolve: unknown;
115-
load: unknown;
116-
};
112+
) as TracingHooksSync;
113+
114+
setDiagnosticsHook(event => {
115+
GLOBAL_OBJ.__SENTRY_ORCHESTRION__ = GLOBAL_OBJ.__SENTRY_ORCHESTRION__ || {};
116+
GLOBAL_OBJ.__SENTRY_ORCHESTRION__.runtime = GLOBAL_OBJ.__SENTRY_ORCHESTRION__.runtime || [];
117+
GLOBAL_OBJ.__SENTRY_ORCHESTRION__.runtime.push(event.moduleName);
118+
});
119+
117120
initialize({ instrumentations: SENTRY_INSTRUMENTATIONS });
118121
mod.registerHooks({ resolve, load });
119122
DEBUG_BUILD && debug.log('[orchestrion] registered diagnostics-channel injection via Module.registerHooks()');
@@ -161,5 +164,6 @@ export function registerDiagnosticsChannelInjection(options?: RegisterDiagnostic
161164
return;
162165
}
163166

164-
g.runtime = true;
167+
GLOBAL_OBJ.__SENTRY_ORCHESTRION__ = GLOBAL_OBJ.__SENTRY_ORCHESTRION__ || {};
168+
GLOBAL_OBJ.__SENTRY_ORCHESTRION__.runtime = GLOBAL_OBJ.__SENTRY_ORCHESTRION__.runtime || [];
165169
}

yarn.lock

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -404,10 +404,10 @@
404404
dependencies:
405405
json-schema-to-ts "^3.1.1"
406406

407-
"@apm-js-collab/code-transformer-bundler-plugins@^0.6.0":
408-
version "0.6.0"
409-
resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer-bundler-plugins/-/code-transformer-bundler-plugins-0.6.0.tgz#ed64bae9e1871366eadc1ef6dabc9ee6ccc53e57"
410-
integrity sha512-Hys7LDskIB/BNrd87GAfYWHRE3mWgcPYonK4W9uxjho4N0JnPKk2YQyOLlqmkyVhj2UzwLRpmbJ4D6uR9O7wyw==
407+
"@apm-js-collab/code-transformer-bundler-plugins@^0.6.1":
408+
version "0.6.1"
409+
resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer-bundler-plugins/-/code-transformer-bundler-plugins-0.6.1.tgz#29bf79167411b8607f02db177250b8f7c16d93e1"
410+
integrity sha512-TDvypsLUk/W202Y1JBE6I2enQalWjbYXKnxpELWEZn6GLoIcGFii69ljh948hdAzKZJ/wg0Uwivz72a/szoOBQ==
411411
dependencies:
412412
"@apm-js-collab/code-transformer" "^0.18.0"
413413
es-module-lexer "^2.1.0"
@@ -426,10 +426,10 @@
426426
semifies "^1.0.0"
427427
source-map "^0.6.0"
428428

429-
"@apm-js-collab/tracing-hooks@^0.12.0":
430-
version "0.12.0"
431-
resolved "https://registry.yarnpkg.com/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.12.0.tgz#b65505d1d075fc8d8f4fa1973c37eaa9a9975b3f"
432-
integrity sha512-U1cDbHOFbeToq5VWNcroBtQpz3hfH39uLkzJ5lorBFVNhHbTNj5MQvP+jODDwxBkG6A/agbQelG15rEoDgnjWg==
429+
"@apm-js-collab/tracing-hooks@^0.13.0":
430+
version "0.13.0"
431+
resolved "https://registry.yarnpkg.com/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.13.0.tgz#20b2f77ec7a0e5dfd9fbf2215b56e2c2b7f41e4b"
432+
integrity sha512-mTvWz9rnQwx1U3h0XPTHaX7bgfkpipLLTQyjlC2cdhQpQEuoLT0AGzoydeoq2NxfEVv6fWOOETcSbb2nptleyw==
433433
dependencies:
434434
"@apm-js-collab/code-transformer" "^0.18.0"
435435
debug "^4.4.1"

0 commit comments

Comments
 (0)