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
4 changes: 4 additions & 0 deletions packages/server-utils/src/orchestrion/apmTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ export interface CodeTransformerPluginOptions {
*
* A single transform can serve many configs — each invocation can branch on
* `state.module.name` or `state.channelName` to tell the sites apart.
*
* An entry named after a built-in transform (e.g. `tracingChannelImport`) overrides it wherever
* the transformer dispatches it, including internal calls; the originals stay reachable on
* `state.transforms.defaults` so an override can chain the default behaviour.
*/
customTransforms?: Record<string, CustomTransform>;
/**
Expand Down
13 changes: 5 additions & 8 deletions packages/server-utils/src/orchestrion/bundler/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,11 @@ export function externalizedModulesWarning(externalizedModules: string[]): strin
* visible to the runtime).
*/
export function orchestrionTransformOptions(options: PluginOptions): CodeTransformerPluginOptions {
const subscribeInjection = options.injectChannelSubscribers ? subscribeInjectionOptions() : undefined;

const instrumentations = [
...SENTRY_INSTRUMENTATIONS,
...(options.instrumentations || []),
...(subscribeInjection?.instrumentations || []),
];
const customTransforms = { ...options.customTransforms, ...subscribeInjection?.customTransforms };
const instrumentations = [...SENTRY_INSTRUMENTATIONS, ...(options.instrumentations || [])];
const customTransforms = {
...options.customTransforms,
...(options.injectChannelSubscribers ? subscribeInjectionOptions().customTransforms : undefined),
};

if (options.shouldInjectDiagnostics === false) {
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import type { CustomTransform } from '../apmTypes';
import { parse } from 'meriyah';
import { SUBSCRIBE_INJECTIONS } from '../config';
import { subscriberExportForModule } from '../config/channel-integration-definitions';
import { SUBSCRIBE_TRANSFORM_NAME } from '../config/subscribe-injection';
import type { PluginOptions } from './options';

// Tracks Program nodes we already injected into, so a package with several
Expand Down Expand Up @@ -38,22 +36,33 @@ function subscribeSnippet(exportName: string, esm: boolean): string {
}

/**
* The custom transform registered under {@link SUBSCRIBE_TRANSFORM_NAME}. It is
* invoked with the matched `Program` node and mutates it in place, splicing the
* marker-push snippet in after any `'use strict'` directive.
* Override for orchestrion's built-in `tracingChannelImport` transform, which
* runs (via `tracingChannelDeclaration`) for every file that gets a channel
* wrapped — the one hook that reaches every instrumented module without any
* extra instrumentation configs. It chains the default (which splices the
* `diagnostics_channel` import and bails when it is already present), then
* splices the marker-push snippet in after any `'use strict'` directive.
*
* `state` carries the matched config spread with `{ moduleType }`; the config's
* `channelName` carries the package name (see `toSubscribeInjections`), which
* maps to the subscriber's export name.
* Invoked once per wrapped channel, so the `WeakSet` keeps the snippet to one
* per file. Requires `@apm-js-collab/code-transformer` >= 0.18.1, where
* built-ins dispatch through the override map and expose the originals on
* `state.transforms.defaults`.
*/
const injectSubscribe: CustomTransform = (state, program) => {
const injectSubscribe: CustomTransform = (state, program, parent, ancestry) => {
const { moduleType, module, transforms } = state as {
moduleType?: string;
module?: { name?: string };
transforms: { defaults: { tracingChannelImport: CustomTransform } };
};

transforms.defaults.tracingChannelImport(state, program, parent, ancestry);
Comment thread
timfish marked this conversation as resolved.

const node = program as ProgramNode;
if (injectedPrograms.has(node)) {
return;
}

const { moduleType, channelName } = state as { moduleType?: string; channelName?: string };
const exportName = channelName ? subscriberExportForModule(channelName) : undefined;
const exportName = module?.name ? subscriberExportForModule(module.name) : undefined;
if (!exportName) {
return;
}
Expand All @@ -70,18 +79,17 @@ const injectSubscribe: CustomTransform = (state, program) => {
};

/**
* The `instrumentations` + `customTransforms` a bundler plugin passes to
* The `customTransforms` a bundler plugin passes to
* {@link orchestrionTransformOptions} to enable the marker-push subscribe
* injection used by bundler-only SDKs (e.g. `@sentry/cloudflare`).
*
* The `SUBSCRIBE_INJECTIONS` configs ride alongside the real channel-publishing
* configs, and `injectSubscribe` runs on each matched module, so every
* transformed package self-registers its subscriber on the global marker
* without a runtime module hook.
* Overriding the built-in `tracingChannelImport` transform makes
* `injectSubscribe` run on every instrumented module, so every transformed
* package self-registers its subscriber on the global marker without a runtime
* module hook — and without a parallel set of injection configs.
*/
export function subscribeInjectionOptions(): Pick<PluginOptions, 'instrumentations' | 'customTransforms'> {
export function subscribeInjectionOptions(): Pick<PluginOptions, 'customTransforms'> {
return {
instrumentations: SUBSCRIBE_INJECTIONS,
customTransforms: { [SUBSCRIBE_TRANSFORM_NAME]: injectSubscribe },
customTransforms: { tracingChannelImport: injectSubscribe },
};
}
4 changes: 1 addition & 3 deletions packages/server-utils/src/orchestrion/config/amqplib.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InstrumentationConfig } from '..';
import { getModuleNames, toSubscribeInjections } from './subscribe-injection';
import { getModuleNames } from './module-names';

// `amqplib` splits its API across three files:
// - `lib/channel_model.js` holds `class Channel` (publish/consume/ack/nack/reject/…) and
Expand Down Expand Up @@ -90,5 +90,3 @@ export const amqplibChannels = {
AMQPLIB_NACK_ALL: 'orchestrion:amqplib:nackAll',
AMQPLIB_CONNECT: 'orchestrion:amqplib:connect',
} as const;

export const amqplibSubscribeInjection = toSubscribeInjections(amqplibConfig);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InstrumentationConfig } from '..';
import { getModuleNames, toSubscribeInjections } from './subscribe-injection';
import { getModuleNames } from './module-names';

export const anthropicAiConfig = [
// One entry each for CJS/ESM
Expand Down Expand Up @@ -41,5 +41,3 @@ export const anthropicAiChannels = {
ANTHROPIC_MODELS: 'orchestrion:@anthropic-ai/sdk:models',
ANTHROPIC_MESSAGES_STREAM: 'orchestrion:@anthropic-ai/sdk:messages-stream',
} as const;

export const anthropicAiSubscribeInjection = toSubscribeInjections(anthropicAiConfig);
4 changes: 1 addition & 3 deletions packages/server-utils/src/orchestrion/config/aws-sdk.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InstrumentationConfig } from '../apmTypes';
import { getModuleNames, toSubscribeInjections } from './subscribe-injection';
import { getModuleNames } from './module-names';

// The AWS SDK (v3) routes every command through the smithy `Client.prototype.send` method. Which
// package hosts that `Client` class changed across versions, so we target all of them; only the one
Expand Down Expand Up @@ -35,5 +35,3 @@ export const awsSdkChannels = {
AWS_SMITHY_CLIENT_SEND: 'orchestrion:@smithy/smithy-client:send',
AWS_SDK_SMITHY_CLIENT_SEND: 'orchestrion:@aws-sdk/smithy-client:send',
} as const;

export const awsSdkSubscribeInjection = toSubscribeInjections(awsSdkConfig);
3 changes: 0 additions & 3 deletions packages/server-utils/src/orchestrion/config/dataloader.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { InstrumentationConfig } from '..';
import { toSubscribeInjections } from './subscribe-injection';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nothing to act in this PR. I just saw that we don't have getModuleNames in this integration. We might have missed that.

cc @isaacs is that something we should add?


// `dataloader` ships a single transpiled CommonJS `index.js`. Its class methods are emitted as
// `_proto.<name> = function <name>() {}` (named function *expressions*), so they match on
Expand Down Expand Up @@ -54,5 +53,3 @@ export const dataloaderChannels = {
DATALOADER_CLEAR: 'orchestrion:dataloader:clear',
DATALOADER_CLEAR_ALL: 'orchestrion:dataloader:clearAll',
} as const;

export const dataloaderSubscribeInjection = toSubscribeInjections(dataloaderConfig);
4 changes: 1 addition & 3 deletions packages/server-utils/src/orchestrion/config/express.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InstrumentationConfig } from '..';
import { getModuleNames, toSubscribeInjections } from './subscribe-injection';
import { getModuleNames } from './module-names';

export const expressConfig = [
// Express funnels every middleware/route handler through a single method on
Expand Down Expand Up @@ -75,5 +75,3 @@ export const expressChannels = {
EXPRESS_REGISTER: 'orchestrion:express:register',
ROUTER_REGISTER: 'orchestrion:router:register',
} as const;

export const expressSubscribeInjection = toSubscribeInjections(expressConfig);
4 changes: 1 addition & 3 deletions packages/server-utils/src/orchestrion/config/firebase.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InstrumentationConfig } from '..';
import { getModuleNames, toSubscribeInjections } from './subscribe-injection';
import { getModuleNames } from './module-names';

// firebase 9+ ships firestore as `@firebase/firestore` (matches the OTel integration's range). Only the
// `lite` SDK exposes the free `addDoc`/`getDocs`/`setDoc`/`deleteDoc` functions we trace, and only the
Expand Down Expand Up @@ -93,5 +93,3 @@ export const firebaseChannels = {
FIREBASE_FUNCTIONS_STORAGE_DELETED: 'orchestrion:firebase-functions:storage-deleted',
FIREBASE_FUNCTIONS_STORAGE_METADATA_UPDATED: 'orchestrion:firebase-functions:storage-metadata-updated',
} as const;

export const firebaseSubscribeInjection = toSubscribeInjections(firebaseConfig);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InstrumentationConfig } from '..';
import { getModuleNames, toSubscribeInjections } from './subscribe-injection';
import { getModuleNames } from './module-names';

// Two shapes of `acquire`, both publishing to the same `orchestrion:generic-pool:acquire` channel:
// - v3+: `class Pool { acquire(priority) }` returns a promise, so `kind: 'Auto'` resolves to `wrapPromise`.
Expand All @@ -24,5 +24,3 @@ export const genericPoolModuleNames = getModuleNames(genericPoolConfig);
export const genericPoolChannels = {
GENERIC_POOL_ACQUIRE: 'orchestrion:generic-pool:acquire',
} as const;

export const genericPoolSubscribeInjection = toSubscribeInjections(genericPoolConfig);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InstrumentationConfig } from '..';
import { getModuleNames, toSubscribeInjections } from './subscribe-injection';
import { getModuleNames } from './module-names';

// `@google/genai` ships one bundled file per module format and the matcher compares `filePath` exactly,
// so we list every file the `node` export condition resolves to across the supported range: `index.js`
Expand Down Expand Up @@ -41,5 +41,3 @@ export const googleGenAiChannels = {
GOOGLE_GENAI_EMBED_CONTENT: 'orchestrion:@google/genai:embed-content',
GOOGLE_GENAI_CHAT: 'orchestrion:@google/genai:chat',
} as const;

export const googleGenAiSubscribeInjection = toSubscribeInjections(googleGenAiConfig);
4 changes: 1 addition & 3 deletions packages/server-utils/src/orchestrion/config/graphql.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InstrumentationConfig } from '..';
import { getModuleNames, toSubscribeInjections } from './subscribe-injection';
import { getModuleNames } from './module-names';

// `parse`/`validate`/`execute` are top-level named `function` declarations in graphql's compiled
// files, stable across the supported majors, so `functionName` matches. `execute` returns
Expand Down Expand Up @@ -29,5 +29,3 @@ export const graphqlChannels = {
} as const;

export const graphqlModuleNames = getModuleNames(graphqlConfig);

export const graphqlSubscribeInjection = toSubscribeInjections(graphqlConfig);
4 changes: 1 addition & 3 deletions packages/server-utils/src/orchestrion/config/hapi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InstrumentationConfig } from '..';
import { getModuleNames, toSubscribeInjections } from './subscribe-injection';
import { getModuleNames } from './module-names';

export const hapiConfig = [
// hapi's `route`/`ext` live on an anonymous class (`internals.Server = class {}`),
Expand All @@ -24,5 +24,3 @@ export const hapiChannels = {
HAPI_ROUTE: 'orchestrion:@hapi/hapi:route',
HAPI_EXT: 'orchestrion:@hapi/hapi:ext',
} as const;

export const hapiSubscribeInjection = toSubscribeInjections(hapiConfig);
101 changes: 29 additions & 72 deletions packages/server-utils/src/orchestrion/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
import type { InstrumentationConfig } from '..';
import { uniq } from '@sentry/core';

import { awsSdkConfig, awsSdkSubscribeInjection } from './aws-sdk';
import { amqplibConfig, amqplibSubscribeInjection } from './amqplib';
import { anthropicAiConfig, anthropicAiSubscribeInjection } from './anthropic-ai';
import { dataloaderConfig, dataloaderSubscribeInjection } from './dataloader';
import { expressConfig, expressSubscribeInjection } from './express';
import { firebaseConfig, firebaseSubscribeInjection } from './firebase';
import { genericPoolConfig, genericPoolSubscribeInjection } from './generic-pool';
import { googleGenAiConfig, googleGenAiSubscribeInjection } from './google-genai';
import { graphqlConfig, graphqlSubscribeInjection } from './graphql';
import { hapiConfig, hapiSubscribeInjection } from './hapi';
import { ioredisConfig, ioredisSubscribeInjection } from './ioredis';
import { kafkajsConfig, kafkajsSubscribeInjection } from './kafkajs';
import { knexConfig, knexSubscribeInjection } from './knex';
import { koaConfig, koaSubscribeInjection } from './koa';
import { langchainConfig, langchainSubscribeInjection } from './langchain';
import { langgraphConfig, langgraphSubscribeInjection } from './langgraph';
import { lruMemoizerConfig, lruMemoizerSubscribeInjection } from './lru-memoizer';
import { mongodbConfig, mongodbSubscribeInjection } from './mongodb';
import { mongooseConfig, mongooseSubscribeInjection } from './mongoose';
import { mysql2Config, mysql2SubscribeInjection } from './mysql2';
import { mysqlConfig, mysqlSubscribeInjection } from './mysql';
import { nestjsConfig, nestjsSubscribeInjection } from './nestjs';
import { openaiConfig, openaiSubscribeInjection } from './openai';
import { pgConfig, pgSubscribeInjection } from './pg';
import { postgresJsConfig, postgresJsSubscribeInjection } from './postgres';
import { redisConfig, redisSubscribeInjection } from './redis';
import { remixConfig, remixSubscribeInjection } from './remix';
import { tediousConfig, tediousSubscribeInjection } from './tedious';
import { vercelAiConfig, vercelAiSubscribeInjection } from './vercel-ai';
import { awsSdkConfig } from './aws-sdk';
import { amqplibConfig } from './amqplib';
import { anthropicAiConfig } from './anthropic-ai';
import { dataloaderConfig } from './dataloader';
import { expressConfig } from './express';
import { firebaseConfig } from './firebase';
import { genericPoolConfig } from './generic-pool';
import { googleGenAiConfig } from './google-genai';
import { graphqlConfig } from './graphql';
import { hapiConfig } from './hapi';
import { ioredisConfig } from './ioredis';
import { kafkajsConfig } from './kafkajs';
import { knexConfig } from './knex';
import { koaConfig } from './koa';
import { langchainConfig } from './langchain';
import { langgraphConfig } from './langgraph';
import { lruMemoizerConfig } from './lru-memoizer';
import { mongodbConfig } from './mongodb';
import { mongooseConfig } from './mongoose';
import { mysql2Config } from './mysql2';
import { mysqlConfig } from './mysql';
import { nestjsConfig } from './nestjs';
import { openaiConfig } from './openai';
import { pgConfig } from './pg';
import { postgresJsConfig } from './postgres';
import { redisConfig } from './redis';
import { remixConfig } from './remix';
import { tediousConfig } from './tedious';
import { vercelAiConfig } from './vercel-ai';
// Kept sorted alphabetically by module so concurrent additions insert at different
// points rather than all appending to the end (fewer merge conflicts).

Expand Down Expand Up @@ -72,49 +72,6 @@ export const SENTRY_INSTRUMENTATIONS: InstrumentationConfig[] = [
...vercelAiConfig,
];

/**
* The `Program`-matching injection configs that make each instrumented file
* self-register its channel subscriber at load (used by bundler-only SDKs like
* `@sentry/cloudflare`).
*
* Deliberately separate from `SENTRY_INSTRUMENTATIONS`: these reference a custom
* transform that only the opted-in bundler plugin registers, so feeding them to
* the runtime `--import` hook (which can't register it) would make the
* code-transformer drop the whole file. Each library owns its own
* `*SubscribeInjection` (derived from its channel configs), collected here.
*/
export const SUBSCRIBE_INJECTIONS: InstrumentationConfig[] = [
...amqplibSubscribeInjection,
...anthropicAiSubscribeInjection,
...awsSdkSubscribeInjection,
...dataloaderSubscribeInjection,
...expressSubscribeInjection,
...firebaseSubscribeInjection,
...genericPoolSubscribeInjection,
...googleGenAiSubscribeInjection,
...graphqlSubscribeInjection,
...hapiSubscribeInjection,
...ioredisSubscribeInjection,
...kafkajsSubscribeInjection,
...knexSubscribeInjection,
...koaSubscribeInjection,
...langchainSubscribeInjection,
...langgraphSubscribeInjection,
...lruMemoizerSubscribeInjection,
...mongodbSubscribeInjection,
...mongooseSubscribeInjection,
...mysql2SubscribeInjection,
...mysqlSubscribeInjection,
...nestjsSubscribeInjection,
...openaiSubscribeInjection,
...pgSubscribeInjection,
...postgresJsSubscribeInjection,
...redisSubscribeInjection,
...remixSubscribeInjection,
...tediousSubscribeInjection,
...vercelAiSubscribeInjection,
];

/**
* The unique set of package names instrumented by `SENTRY_INSTRUMENTATIONS`
* merged with any caller-provided `instrumentations` (e.g. `['mysql']`).
Expand Down
4 changes: 1 addition & 3 deletions packages/server-utils/src/orchestrion/config/ioredis.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InstrumentationConfig } from '..';
import { getModuleNames, toSubscribeInjections } from './subscribe-injection';
import { getModuleNames } from './module-names';

export const ioredisConfig = [
// ioredis `<5.11.0` (>=5.11.0 publishes its own `ioredis:*` diagnostics_channel)
Expand Down Expand Up @@ -33,5 +33,3 @@ export const ioredisChannels = {
IOREDIS_COMMAND: 'orchestrion:ioredis:command',
IOREDIS_CONNECT: 'orchestrion:ioredis:connect',
} as const;

export const ioredisSubscribeInjection = toSubscribeInjections(ioredisConfig);
4 changes: 1 addition & 3 deletions packages/server-utils/src/orchestrion/config/kafkajs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { InstrumentationConfig } from '..';
import { getModuleNames, toSubscribeInjections } from './subscribe-injection';
import { getModuleNames } from './module-names';

export const kafkajsConfig = [
{
Expand Down Expand Up @@ -28,5 +28,3 @@ export const kafkajsChannels = {
KAFKAJS_SEND_BATCH: 'orchestrion:kafkajs:send_batch',
KAFKAJS_CONSUMER_RUN: 'orchestrion:kafkajs:consumer_run',
} as const;

export const kafkajsSubscribeInjection = toSubscribeInjections(kafkajsConfig);
3 changes: 0 additions & 3 deletions packages/server-utils/src/orchestrion/config/knex.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { InstrumentationConfig } from '..';
import { toSubscribeInjections } from './subscribe-injection';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


const MODULE_NAME = 'knex';

Expand Down Expand Up @@ -52,5 +51,3 @@ export const knexChannels = {
KNEX_SCHEMA_BUILDER: 'orchestrion:knex:schemaBuilder',
KNEX_RAW: 'orchestrion:knex:raw',
} as const;

export const knexSubscribeInjection = toSubscribeInjections(knexConfig);
Loading
Loading