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
1 change: 1 addition & 0 deletions packages/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export {
} from '@sentry/core';
export { trpcMiddleware, wrapMcpServerWithSentry } from '@sentry/core/server';
export { createFlueInstrumentation, instrumentPostgresJsSql } from '@sentry/server-utils';
export type { FlueOptions } from '@sentry/server-utils';

export { withSentry } from './withSentry';
export { defineCloudflareOptions } from './defineCloudflareOptions';
Expand Down
75 changes: 75 additions & 0 deletions packages/cloudflare/src/vite/flueRuntime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { createRequire } from 'node:module';

@isaacs isaacs Sep 18, 2026

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.

Main issue/comment I'd make for this PR: this file is nearly identical to the packages/cloudflare/src/vite/mastraObservability.ts file, except for the module specifier, the identifier, the target regex, the tolerated resolve error, and getter versus assignment.

Suggestion: extract one factory, eg createProvidedModulePlugin({ name, moduleName, identifier, targetId, lazy }), and let both call sites shrink to a few lines. That also gives one place to fix any other concerns for both packages.

Also, I notice that Mastra's plain catch { return; } works today only because @mastra/observability still publishes a require condition. If it goes ESM-only, that provider silently stops injecting, with the same symptom this branch just fixed for Flue. A shared check removes that potential future bug, and lets us improve both in one place.

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.

By the way, this can definitely be put off for a future PR, I just think we should probably get to it before there's a third one of these, and we start having a harder time deciding which drifting behavior is correct 😅

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

will take it up on a follow up. you're right about the mastra half, it isn't hypothetical either, @mastra/observability@1.17.4 still ships a require condition, so mastraObservability.ts:48's bare catch { return; } works by luck, an esm only release turns injection off silently

fixed the flue side here and left mastra untouched so both land together with the rafactor

import { resolve } from 'node:path';
import MagicString from 'magic-string';

// Namespace binding the injected provider import uses; read back by the integration
// off the global marker.
const PROVIDER_IDENTIFIER = '__SENTRY_FLUE_RUNTIME__';

const FLUE_MODULE = '@flue/runtime';

// The bundled `@sentry/server-utils` Flue integration module (ESM build — the only one a
// worker loads). It reads `@flue/runtime` off the global marker this provider populates,
// because `instrument()` registers into module-scope state no channel payload can carry.
const FLUE_INTEGRATION_ID = /@sentry\/server-utils\/build\/esm\/integrations\/flue\.js$/;

/** Whether `id` is the Sentry Flue integration module the provider injects into. */
export function isFlueIntegrationModuleId(id: string): boolean {
const normalizedId = id.replace(/\\/g, '/').replace(/[?#].*$/, '');
return FLUE_INTEGRATION_ID.test(normalizedId);
}

/**
* Splices a static `import * as … from '@flue/runtime'` into Sentry's own Flue integration module
* and exposes the namespace on the global orchestrion marker.
*
* Flue is registered rather than patched — `instrument()` writes into module-scope state — so
* instrumenting it needs that module's own binding, and no channel payload carries one. On Node the
* user passes it by calling `instrument()` themselves; a bundled worker has no `node_modules` to
* resolve from, so it is supplied at build time instead.
*/
export function sentryFlueRuntimeProviderPlugin(): {
name: string;
configResolved(config: { root: string }): void;
transform(code: string, id: string): { code: string; map: ReturnType<MagicString['generateMap']> } | undefined;
} {
let providerSnippet: string | undefined;

return {
name: 'sentry-cloudflare-flue-runtime-provider',

configResolved(config: { root: string }): void {
// Build-time only; never ships to the worker. Probed with CJS resolution, which an ESM-only
// `@flue/runtime` fails with `ERR_PACKAGE_PATH_NOT_EXPORTED` — so only a module-not-found
// counts as absent, and any other failure still injects and lets Vite report it. Not
// `import.meta.resolve`: `parentURL` is ignored without a flag, and it is absent from the
// CJS build.
try {
createRequire(resolve(config.root, 'noop.js')).resolve(FLUE_MODULE);
} catch (error) {
const code = (error as NodeJS.ErrnoException | undefined)?.code;
if (code === 'MODULE_NOT_FOUND' || code === 'ERR_MODULE_NOT_FOUND') {
return;
}
}
Comment on lines +47 to +54

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.

Based on the comment above, it seems safer to detect module not found rather than "anything other than path not exported"?

Suggested change
try {
createRequire(resolve(config.root, 'noop.js')).resolve(FLUE_MODULE);
} catch (error) {
if ((error as NodeJS.ErrnoException | undefined)?.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
return;
}
}
try {
createRequire(resolve(config.root, 'noop.js')).resolve(FLUE_MODULE);
} catch (error) {
const code = (error as NodeJS.ErrnoException | undefined)?.code;
if (code === 'MODULE_NOT_FOUND' || code === 'ERR_MODULE_NOT_FOUND') {
return;
}
}

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.

Oh, also, it'd be a bigger refactor, but I think if we have a rollup context, we can do await this.resolve(FLUE_MODULE, resolve(root, 'noop.js')) on it to get a more definitive answer, regardless of export type. That would drop createRequire, node:path and the error-code special case entirely.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

this.resolve() version is def nicer, I'll do it once with the refactor

// A getter where Mastra assigns: the bundler may evaluate Sentry's module before
// `@flue/runtime` is initialized, and assigning there would store `undefined`.
providerSnippet =
`import * as ${PROVIDER_IDENTIFIER} from '${FLUE_MODULE}';\n` +
'(globalThis.__SENTRY_ORCHESTRION__ = globalThis.__SENTRY_ORCHESTRION__ || {});\n' +
'(globalThis.__SENTRY_ORCHESTRION__.providedModules = globalThis.__SENTRY_ORCHESTRION__.providedModules || {});\n' +
`Object.defineProperty(globalThis.__SENTRY_ORCHESTRION__.providedModules, '${FLUE_MODULE}', ` +
`{ configurable: true, enumerable: true, get() { return ${PROVIDER_IDENTIFIER}; } });\n`;
},

transform(code: string, id: string): { code: string; map: ReturnType<MagicString['generateMap']> } | undefined {
// `code.includes` keeps this idempotent: a second pass over already-injected output would
// otherwise emit a duplicate `import * as` binding, which is a syntax error.
if (!providerSnippet || !isFlueIntegrationModuleId(id) || code.includes(PROVIDER_IDENTIFIER)) return undefined;

const ms = new MagicString(code);
ms.prepend(providerSnippet);
return { code: ms.toString(), map: ms.generateMap({ hires: true }) };
},
Comment on lines +65 to +73

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.

This is not idempotent, because the ms.prepend unconditionally adds the snippet.

If transform ever sees the same module twice in one environment, the output carries two import * as __SENTRY_FLUE_RUNTIME__ statements, which is a duplicate binding and a syntax error. Vite's per-environment module graphs make it unlikely, but it's a potential future hazard.

(Note: same thing in the Mastra plugin, probably another reason to consider consolidating them.)

Suggested change
transform(code: string, id: string): { code: string; map: ReturnType<MagicString['generateMap']> } | undefined {
if (!providerSnippet || !isFlueIntegrationModuleId(id)) return undefined;
const ms = new MagicString(code);
ms.prepend(providerSnippet);
return { code: ms.toString(), map: ms.generateMap({ hires: true }) };
},
transform(code: string, id: string): { code: string; map: ReturnType<MagicString['generateMap']> } | undefined {
if (!providerSnippet || !isFlueIntegrationModuleId(id) || code.includes(PROVIDER_IDENTIFIER)) {
return undefined;
}
const ms = new MagicString(code);
ms.prepend(providerSnippet);
return { code: ms.toString(), map: ms.generateMap({ hires: true }) };
},

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I couldn't find a config where vite actually re-transforms its own output, but added it anyway

left the mastra copy as is so it lands with the factory

};
}
2 changes: 2 additions & 0 deletions packages/cloudflare/src/vite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// expose it — same setup as `@sentry/server-utils/orchestrion/vite` itself.
import { sentryOrchestrionPlugin } from '@sentry/server-utils/orchestrion/vite';
import { sentryCloudflareAutoInstrumentPlugin } from './autoInstrument';
import { sentryFlueRuntimeProviderPlugin } from './flueRuntime';
import { sentryMastraObservabilityProviderPlugin } from './mastraObservability';

/**
Expand Down Expand Up @@ -91,6 +92,7 @@ export function sentryCloudflareVitePlugin(options: SentryCloudflareVitePluginOp
dcModule: '@sentry/cloudflare/orchestrion-diagnostics-channel',
}),
sentryMastraObservabilityProviderPlugin(),
sentryFlueRuntimeProviderPlugin(),
...(options.autoInstrumentation !== false
? [sentryCloudflareAutoInstrumentPlugin({ wranglerConfigPath: options.wranglerConfigPath })]
: []),
Expand Down
162 changes: 162 additions & 0 deletions packages/cloudflare/test/vite/flueRuntime.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { beforeAll, describe, expect, it } from 'vitest';
import { sentryCloudflareVitePlugin } from '../../src/vite/index';
import { isFlueIntegrationModuleId, sentryFlueRuntimeProviderPlugin } from '../../src/vite/flueRuntime';

const PROVIDER_PLUGIN = 'sentry-cloudflare-flue-runtime-provider';
const FLUE_INTEGRATION_MODULE = '/app/node_modules/@sentry/server-utils/build/esm/integrations/flue.js';

/** An app root whose `node_modules` holds an ESM-only `@flue/runtime`, as published. */
function createRootWithFlue(): string {
const root = mkdtempSync(join(tmpdir(), 'sentry-flue-root-'));
const pkgDir = join(root, 'node_modules', '@flue', 'runtime');
mkdirSync(join(pkgDir, 'dist'), { recursive: true });
writeFileSync(
join(pkgDir, 'package.json'),
// No `require` condition — the reason `resolve()` reports ERR_PACKAGE_PATH_NOT_EXPORTED.
JSON.stringify({
name: '@flue/runtime',
version: '2.0.8',
type: 'module',
exports: { '.': { import: './dist/index.mjs' } },
}),
);
writeFileSync(join(pkgDir, 'dist', 'index.mjs'), 'export const instrument = () => {};\n');
return root;
}

function createEmptyRoot(): string {
return mkdtempSync(join(tmpdir(), 'sentry-flue-empty-'));
}

/** An app root holding an installed but unreadable `@flue/runtime`. */
function createRootWithBrokenFlue(): string {
const root = mkdtempSync(join(tmpdir(), 'sentry-flue-broken-'));
const pkgDir = join(root, 'node_modules', '@flue', 'runtime');
mkdirSync(pkgDir, { recursive: true });
writeFileSync(join(pkgDir, 'package.json'), '{ not json');
return root;
}

describe('isFlueIntegrationModuleId', () => {
it('matches the ESM Flue integration module', () => {
expect(isFlueIntegrationModuleId(FLUE_INTEGRATION_MODULE)).toBe(true);
});

it('ignores a trailing query/hash Vite may append', () => {
expect(isFlueIntegrationModuleId(`${FLUE_INTEGRATION_MODULE}?v=abc`)).toBe(true);
});

it('normalizes Windows separators', () => {
expect(
isFlueIntegrationModuleId('C:\\app\\node_modules\\@sentry\\server-utils\\build\\esm\\integrations\\flue.js'),
).toBe(true);
});

it('does not match the CJS build (workers load ESM)', () => {
expect(isFlueIntegrationModuleId('/app/node_modules/@sentry/server-utils/build/cjs/integrations/flue.js')).toBe(
false,
);
});

it('does not match another integration module', () => {
expect(isFlueIntegrationModuleId('/app/node_modules/@sentry/server-utils/build/esm/integrations/mastra.js')).toBe(
false,
);
});

it('does not match Flue itself', () => {
expect(isFlueIntegrationModuleId('/app/node_modules/@flue/runtime/dist/index.mjs')).toBe(false);
});
});

describe('sentryFlueRuntimeProviderPlugin', () => {
describe('when the app has @flue/runtime installed', () => {
let root: string;

beforeAll(() => {
root = createRootWithFlue();
});

it('injects the provider even though the package is ESM-only', () => {
// Regression guard: treating that error as "absent" silently disabled auto-instrumentation.
const plugin = sentryFlueRuntimeProviderPlugin();
plugin.configResolved({ root });

const result = plugin.transform('export const x = 1;', FLUE_INTEGRATION_MODULE);

expect(result?.code).toContain("import * as __SENTRY_FLUE_RUNTIME__ from '@flue/runtime';");
expect(result?.code).toContain('__SENTRY_ORCHESTRION__.providedModules');
expect(result?.code).toContain('export const x = 1;');
});

it('exposes the namespace through a getter rather than a snapshot', () => {
const plugin = sentryFlueRuntimeProviderPlugin();
plugin.configResolved({ root });

expect(plugin.transform('', FLUE_INTEGRATION_MODULE)?.code).toContain(
'get() { return __SENTRY_FLUE_RUNTIME__; }',
);
});

it('leaves every other module untouched', () => {
const plugin = sentryFlueRuntimeProviderPlugin();
plugin.configResolved({ root });

expect(plugin.transform('export const x = 1;', '/app/src/index.ts')).toBeUndefined();
});

it('injects once, so a second pass cannot emit a duplicate binding', () => {
const plugin = sentryFlueRuntimeProviderPlugin();
plugin.configResolved({ root });

const once = plugin.transform('export const x = 1;', FLUE_INTEGRATION_MODULE)?.code ?? '';

expect(plugin.transform(once, FLUE_INTEGRATION_MODULE)).toBeUndefined();
});
});

describe('when @flue/runtime is installed but unresolvable', () => {
it('still injects, so the failure surfaces from Vite instead of silently disabling tracing', () => {
// Only a module-not-found means absent. Skipping on every other resolve failure is how an
// installed package silently loses instrumentation, which is the bug this plugin fixes.
const plugin = sentryFlueRuntimeProviderPlugin();
plugin.configResolved({ root: createRootWithBrokenFlue() });

expect(plugin.transform('', FLUE_INTEGRATION_MODULE)).toBeDefined();
});
});

describe('when the app does not have @flue/runtime installed', () => {
it('injects nothing', () => {
const plugin = sentryFlueRuntimeProviderPlugin();
plugin.configResolved({ root: createEmptyRoot() });

expect(plugin.transform('export const x = 1;', FLUE_INTEGRATION_MODULE)).toBeUndefined();
});

it("resolves from the app root, not from Sentry's own install", () => {
// This repo has no `@flue/runtime`, so only an app root that does can pass the check.
const withFlue = sentryFlueRuntimeProviderPlugin();
withFlue.configResolved({ root: createRootWithFlue() });

const withoutFlue = sentryFlueRuntimeProviderPlugin();
withoutFlue.configResolved({ root: createEmptyRoot() });

expect(withFlue.transform('', FLUE_INTEGRATION_MODULE)).toBeDefined();
expect(withoutFlue.transform('', FLUE_INTEGRATION_MODULE)).toBeUndefined();
});
});
});

describe('sentryCloudflareVitePlugin', () => {
it('always includes the Flue runtime provider plugin', () => {
expect(sentryCloudflareVitePlugin().map(plugin => plugin.name)).toContain(PROVIDER_PLUGIN);
// Not gated by auto-instrumentation: it injects into Sentry's own module, not the entry.
expect(sentryCloudflareVitePlugin({ autoInstrumentation: false }).map(plugin => plugin.name)).toContain(
PROVIDER_PLUGIN,
);
});
});
4 changes: 4 additions & 0 deletions packages/server-utils/src/ai/flue/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export const FLUE_INTEGRATION_NAME = 'Flue' as const;

export const FLUE_MODULE_NAME = '@flue/runtime';

export const FLUE_ORIGIN = 'auto.ai.flue';

/**
Expand Down
1 change: 1 addition & 0 deletions packages/server-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export { knexIntegration } from './integrations/knex';
export { langChainIntegration } from './integrations/langchain';
export { langGraphIntegration } from './integrations/langgraph';
export { createFlueInstrumentation } from './ai/flue';
export { flueIntegration } from './integrations/flue';
Comment thread
RulaKhaled marked this conversation as resolved.
export type { FlueOptions } from './ai/flue';
export { mastraIntegration } from './integrations/mastra';
export { SentryMastraExporter } from './ai/mastra';
Expand Down
67 changes: 67 additions & 0 deletions packages/server-utils/src/integrations/flue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { IntegrationFn } from '@sentry/core';
import { debug, defineIntegration, GLOBAL_OBJ } from '@sentry/core';
import type { FlueOptions } from '../ai/flue';
import { createFlueInstrumentation } from '../ai/flue';
import { FLUE_INTEGRATION_NAME, FLUE_MODULE_NAME } from '../ai/flue/constants';
import { DEBUG_BUILD } from '../debug-build';

type FlueInstrumentFn = (instrumentation: ReturnType<typeof createFlueInstrumentation>) => unknown;

/**
* The `instrument` we last registered against, so a second `setup()` in the same isolate is a
* no-op. Cloudflare runs `init()` per request, and with `cacheClient: false` that reaches here
* every time: in production Flue throws on the repeat, and under `vite dev` it disposes our
* previous registration instead — ending the turn and tool spans of every in-flight request.
*
* Keyed on the binding rather than a bare boolean so a fresh `@flue/runtime` instance (a new
* isolate reusing this module, a test swapping the marker) still registers.
*/
let registeredBinding: FlueInstrumentFn | undefined;

/**
* Register the instrumentation with Flue on the user's behalf, when the runtime binding is available.
*
* Flue is registered rather than patched — `instrument()` writes into module-scope state — so this
* needs a reference to that module's own binding. In a bundled worker there is no `node_modules` to
* resolve one from, so `@sentry/cloudflare/vite` splices a static `@flue/runtime` import into this
* module at build time and stashes the namespace on the global marker. Outside that setup the marker
* is empty and this no-ops, leaving the user's own `instrument(Sentry.createFlueInstrumentation())`
* as the way in.
*/
const _flueIntegration = ((options: FlueOptions = {}) => {
return {
name: FLUE_INTEGRATION_NAME,
setup() {
const provided = GLOBAL_OBJ.__SENTRY_ORCHESTRION__?.providedModules?.[FLUE_MODULE_NAME];
const instrument = provided?.instrument as FlueInstrumentFn | undefined;

if (typeof instrument !== 'function') {
DEBUG_BUILD && debug.log('[Flue] no provided `@flue/runtime` binding; skipping auto-registration');
return;
}

if (instrument === registeredBinding) {
DEBUG_BUILD && debug.log('[Flue] already registered in this isolate; skipping auto-registration');
return;
}

try {
instrument(createFlueInstrumentation(options));
registeredBinding = instrument;
} catch (error) {
// Never rethrow: `setup()` runs inside `Sentry.init()`, which core calls unguarded and
// Cloudflare calls per request, so throwing here would take down the request handler.
if ((error as Error | undefined)?.name === 'InstrumentationAlreadyInstalledError') {
// The app owns the key and we will never win it, so stop rebuilding the instrumentation
// (two 1000-entry `LRUMap`s) on every later `init()`.
registeredBinding = instrument;
DEBUG_BUILD && debug.log('[Flue] already instrumented by the app; skipping auto-registration');
} else {
debug.warn('[Flue] auto-registration failed; Flue spans will not be recorded:', error);
}
Comment thread
RulaKhaled marked this conversation as resolved.
}
Comment thread
RulaKhaled marked this conversation as resolved.
Comment on lines +48 to +62

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.

In dev, a repeat instrument() under the same key doesn't throw, and instead disposes the previous registration. Sentry's dispose() (in packages/server-utils/src/ai/flue/index.ts) ends every tracked turn and tool span and clears all three maps.

Cloudflare calls Sentry.init() per request. With the default cacheClient: true the cached client short-circuits before setup() reruns, so this doesn't fire.

With cacheClient: false, or any path that bypasses the cache, setup() runs per request.

Under vite dev that means every request ends the in-flight turn and tool spans of every concurrent request, and the fresh registration starts with empty maps so those spans are then orphaned, and nothing throws or is logged.

Suggesgtion: guard the call with a module-scope flag, for example let registered = false; set after a successful instrument(). One registration per isolate is all the design wants, and the flag also avoids allocating two 1000-entry LRUMaps per request just to throw them away on the production path.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Very nice. I guarded this on the binding rather than a bare boolean, so a fresh isolate still registers

},
};
}) satisfies IntegrationFn;

export const flueIntegration = defineIntegration(_flueIntegration);
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const CHANNEL_INTEGRATION_DEFINITIONS = [
},
{ exportName: 'langGraphIntegration', modules: ['@langchain/langgraph'] },
{ exportName: 'mastraIntegration', modules: ['@mastra/core'] },
{ exportName: 'flueIntegration', modules: ['@flue/runtime'] },
{ exportName: 'awsIntegration', modules: ['@aws-sdk/smithy-client', '@smithy/core', '@smithy/smithy-client'] },
{ exportName: 'firebaseIntegration', modules: ['@firebase/firestore', 'firebase-functions'] },
{ exportName: 'amqplibIntegration', modules: ['amqplib'] },
Expand Down
12 changes: 12 additions & 0 deletions packages/server-utils/src/orchestrion/config/flue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { InstrumentationConfig } from '../apmTypes';
import { registrationOnly } from './registration-only';

/**
* Flue publishes no diagnostics channels and needs none: it is instrumented by registering with
* `instrument()`, not by patching call sites. Transforming the entry is only how the module's
* integration gets registered at evaluation time, which is what installs it on a bundler-only SDK
* like `@sentry/cloudflare`.
*/
export const flueConfig = [
registrationOnly({ name: '@flue/runtime', versionRange: '>=2.0.0 <3.0.0', filePath: 'dist/index.mjs' }),
] satisfies InstrumentationConfig[];
2 changes: 2 additions & 0 deletions packages/server-utils/src/orchestrion/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { koaConfig } from './koa';
import { langchainConfig } from './langchain';
import { langgraphConfig } from './langgraph';
import { lruMemoizerConfig } from './lru-memoizer';
import { flueConfig } from './flue';
import { mastraConfig } from './mastra';
import { mistralConfig } from './mistral';
import { mongodbConfig } from './mongodb';
Expand Down Expand Up @@ -67,6 +68,7 @@ export const SENTRY_INSTRUMENTATIONS: InstrumentationConfig[] = [
...langchainConfig,
...langgraphConfig,
...lruMemoizerConfig,
...flueConfig,
...mastraConfig,
...mistralConfig,
...mongodbConfig,
Expand Down
Loading
Loading