From fa684ab597912be871e79168ae60d6919da07fab Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 6 Nov 2024 10:31:25 +0100 Subject: [PATCH 01/24] fix(browser): Avoid recording long animation frame spans starting before their parent span (#14186) - Check for start time of parent navigation span and don't start long animation frame span if its start timestamp is earlier than the navigation start time stamp - Refactor span starting logic to use common helper function to compensate the bundle size increase - Add regression test that failed previously - Improve regression test from #14183 to avoid flakes and improve the in-test navigation --- .../init.js | 17 ++++++++++ .../subject.js | 18 +++++++++++ .../template.html | 13 ++++++++ .../test.ts | 31 +++++++++++++++++++ .../long-tasks-before-navigation/init.js | 1 - .../long-tasks-before-navigation/subject.js | 2 +- .../long-tasks-before-navigation/test.ts | 4 ++- .../src/metrics/browserMetrics.ts | 20 ++++++++---- 8 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/template.html create mode 100644 dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/init.js new file mode 100644 index 000000000000..f00d680435bb --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/init.js @@ -0,0 +1,17 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ + idleTimeout: 9000, + enableLongTask: false, + enableLongAnimationFrame: true, + instrumentPageLoad: false, + enableInp: false, + }), + ], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/subject.js new file mode 100644 index 000000000000..b02ed6efa33b --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/subject.js @@ -0,0 +1,18 @@ +function getElapsed(startTime) { + const time = Date.now(); + return time - startTime; +} + +function handleClick() { + const startTime = Date.now(); + while (getElapsed(startTime) < 105) { + // + } + window.history.pushState({}, '', `#myHeading`); +} + +const button = document.getElementById('clickme'); + +console.log('button', button); + +button.addEventListener('click', handleClick); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/template.html new file mode 100644 index 000000000000..1d883292beb0 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/template.html @@ -0,0 +1,13 @@ + + + + + + + + +

My Heading

+ + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/test.ts new file mode 100644 index 000000000000..e6fb88232d63 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-before-navigation/test.ts @@ -0,0 +1,31 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest( + "doesn't capture long animation frame that starts before a navigation.", + async ({ browserName, getLocalTestPath, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + const url = await getLocalTestPath({ testDir: __dirname }); + + await page.goto(url); + + const navigationTransactionEventPromise = getFirstSentryEnvelopeRequest(page); + + await page.locator('#clickme').click(); + + const navigationTransactionEvent = await navigationTransactionEventPromise; + + expect(navigationTransactionEvent.contexts?.trace?.op).toBe('navigation'); + + const loafSpans = navigationTransactionEvent.spans?.filter(s => s.op?.startsWith('ui.long-animation-frame')); + + expect(loafSpans?.length).toEqual(0); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js index 1f396416d855..5986089e5aa4 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/init.js @@ -15,5 +15,4 @@ Sentry.init({ }), ], tracesSampleRate: 1, - debug: true, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/subject.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/subject.js index 2c477161b9f4..d814f8875715 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/subject.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/subject.js @@ -13,5 +13,5 @@ longTaskButton?.addEventListener('click', () => { } // trigger a navigation in the same event loop tick - window.history.pushState({}, '', '/#myHeading'); + window.history.pushState({}, '', '#myHeading'); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts index fe2efb6b3565..d7504eba840c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-before-navigation/test.ts @@ -15,9 +15,11 @@ sentryTest( await page.goto(url); + const navigationTransactionEventPromise = getFirstSentryEnvelopeRequest(page); + await page.locator('#myButton').click(); - const navigationTransactionEvent = await getFirstSentryEnvelopeRequest(page, url); + const navigationTransactionEvent = await navigationTransactionEventPromise; expect(navigationTransactionEvent.contexts?.trace?.op).toBe('navigation'); diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 670b1ce4ae25..f12e5c154ae8 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -143,7 +143,8 @@ export function startTrackingLongAnimationFrames(): void { // we directly observe `long-animation-frame` events instead of through the web-vitals // `observe` helper function. const observer = new PerformanceObserver(list => { - if (!getActiveSpan()) { + const parent = getActiveSpan(); + if (!parent) { return; } for (const entry of list.getEntries() as PerformanceLongAnimationFrameTiming[]) { @@ -152,6 +153,17 @@ export function startTrackingLongAnimationFrames(): void { } const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); + + const { start_timestamp: parentStartTimestamp, op: parentOp } = spanToJSON(parent); + + if (parentOp === 'navigation' && parentStartTimestamp && startTime < parentStartTimestamp) { + // Skip adding the span if the long animation frame started before the navigation started. + // `startAndEndSpan` will otherwise adjust the parent's start time to the span's start + // time, potentially skewing the duration of the actual navigation as reported via our + // routing instrumentations + continue; + } + const duration = msToSec(entry.duration); const attributes: SpanAttributes = { @@ -172,15 +184,11 @@ export function startTrackingLongAnimationFrames(): void { attributes['browser.script.source_char_position'] = sourceCharPosition; } - const span = startInactiveSpan({ + startAndEndSpan(parent, startTime, startTime + duration, { name: 'Main UI thread blocked', op: 'ui.long-animation-frame', - startTime, attributes, }); - if (span) { - span.end(startTime + duration); - } } }); From 0c3656490c6375999755086cdaf2280df03906f7 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 6 Nov 2024 10:32:59 +0100 Subject: [PATCH 02/24] ref(browser): Ensure start time of interaction root and child span is aligned (#14188) Replace the manual span start and end calls for interaction child spans with our `startAndEndSpan` helper. Using `startAndEndSpan` also will adjust the root span's start time stamp if the (in this case interaction-) child span starts earlier than the parent span. --- packages/browser-utils/src/metrics/browserMetrics.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index f12e5c154ae8..278ce9c9784e 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -200,7 +200,8 @@ export function startTrackingLongAnimationFrames(): void { */ export function startTrackingInteractions(): void { addPerformanceInstrumentationHandler('event', ({ entries }) => { - if (!getActiveSpan()) { + const parent = getActiveSpan(); + if (!parent) { return; } for (const entry of entries) { @@ -222,10 +223,7 @@ export function startTrackingInteractions(): void { spanOptions.attributes['ui.component_name'] = componentName; } - const span = startInactiveSpan(spanOptions); - if (span) { - span.end(startTime + duration); - } + startAndEndSpan(parent, startTime, startTime + duration, spanOptions); } } }); From 375091412a89aaba0da082de4702f4445e09cd6d Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Wed, 6 Nov 2024 12:23:23 +0100 Subject: [PATCH 03/24] ref(nextjs): Make build-time value injection turbopack compatible (#14081) Ref: https://github.com/getsentry/sentry-javascript/issues/8105 To inject build-time variables, in addition to doing so via a custom loader, we will be injecting them via the `env` option. Caveat: We are currently using the Next.js build ID as a release name. This build id is passed to the `webpack` option. Since the `webpack` option doesn't exist for turbopack we don't have access to the build ID. For now we will simply not inject a release name, which may be better anyhow since turbopack is currently only stable for dev. --- .size-limit.js | 2 +- .../src/metrics/browserMetrics.ts | 2 +- packages/nextjs/src/client/index.ts | 7 +++- packages/nextjs/src/client/tunnelRoute.ts | 4 +- .../devErrorSymbolicationEventProcessor.ts | 4 +- packages/nextjs/src/config/types.ts | 4 +- packages/nextjs/src/config/webpack.ts | 10 +++-- .../nextjs/src/config/withSentryConfig.ts | 40 +++++++++++++++++++ packages/nextjs/src/edge/index.ts | 4 +- .../src/edge/rewriteFramesIntegration.ts | 7 ++-- packages/nextjs/src/server/index.ts | 12 +++--- .../src/server/rewriteFramesIntegration.ts | 4 +- packages/nextjs/test/serverSdk.test.ts | 2 +- .../nextjs/test/utils/tunnelRoute.test.ts | 14 +++---- 14 files changed, 82 insertions(+), 34 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index 8b506b8f683b..4903d38fef62 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -79,7 +79,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'), gzip: true, - limit: '78.1 KB', + limit: '78.2 KB', }, { name: '@sentry/browser (incl. Tracing, Replay, Feedback)', diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 278ce9c9784e..09714e90c11f 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -1,5 +1,5 @@ /* eslint-disable max-lines */ -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan, startInactiveSpan } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan } from '@sentry/core'; import { setMeasurement } from '@sentry/core'; import type { Measurements, Span, SpanAttributes, StartSpanOptions } from '@sentry/types'; import { browserPerformanceTimeOrigin, getComponentName, htmlTreeAsString, logger, parseUrl } from '@sentry/utils'; diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index c50bbce37305..a1e6aba11308 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -16,7 +16,7 @@ export * from '@sentry/react'; export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __rewriteFramesAssetPrefixPath__: string; + _sentryRewriteFramesAssetPrefixPath: string; }; // Treeshakable guard to remove all code related to tracing @@ -64,7 +64,10 @@ function getDefaultIntegrations(options: BrowserOptions): Integration[] { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const assetPrefixPath = globalWithInjectedValues.__rewriteFramesAssetPrefixPath__ || ''; + const assetPrefixPath = + process.env._sentryRewriteFramesAssetPrefixPath || + globalWithInjectedValues._sentryRewriteFramesAssetPrefixPath || + ''; customDefaultIntegrations.push(nextjsClientStackFrameNormalizationIntegration({ assetPrefixPath })); return customDefaultIntegrations; diff --git a/packages/nextjs/src/client/tunnelRoute.ts b/packages/nextjs/src/client/tunnelRoute.ts index 3c93b93e41f2..59ce4cfe82b7 100644 --- a/packages/nextjs/src/client/tunnelRoute.ts +++ b/packages/nextjs/src/client/tunnelRoute.ts @@ -4,14 +4,14 @@ import { GLOBAL_OBJ, dsnFromString, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../common/debug-build'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __sentryRewritesTunnelPath__?: string; + _sentryRewritesTunnelPath?: string; }; /** * Applies the `tunnel` option to the Next.js SDK options based on `withSentryConfig`'s `tunnelRoute` option. */ export function applyTunnelRouteOption(options: BrowserOptions): void { - const tunnelRouteOption = globalWithInjectedValues.__sentryRewritesTunnelPath__; + const tunnelRouteOption = process.env._sentryRewritesTunnelPath || globalWithInjectedValues._sentryRewritesTunnelPath; if (tunnelRouteOption && options.dsn) { const dsnComponents = dsnFromString(options.dsn); if (!dsnComponents) { diff --git a/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts b/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts index 6c37859a851d..143bf6fef6ef 100644 --- a/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts +++ b/packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts @@ -11,7 +11,7 @@ type OriginalStackFrameResponse = { }; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __sentryBasePath?: string; + _sentryBasePath?: string; }; async function resolveStackFrame( @@ -32,7 +32,7 @@ async function resolveStackFrame( params.append(key, (frame[key as keyof typeof frame] ?? '').toString()); }); - let basePath = globalWithInjectedValues.__sentryBasePath ?? ''; + let basePath = process.env._sentryBasePath ?? globalWithInjectedValues._sentryBasePath ?? ''; // Prefix the basepath with a slash if it doesn't have one if (basePath !== '' && !basePath.match(/^\//)) { diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index d887369606b6..14be35215cf2 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -47,6 +47,8 @@ export type NextConfigObject = { clientTraceMetadata?: string[]; }; productionBrowserSourceMaps?: boolean; + // https://nextjs.org/docs/pages/api-reference/next-config-js/env + env?: Record; }; export type SentryBuildOptions = { @@ -548,7 +550,7 @@ export type ModuleRuleUseProperty = { * Global with values we add when we inject code into people's pages, for use at runtime. */ export type EnhancedGlobal = typeof GLOBAL_OBJ & { - __rewriteFramesDistDir__?: string; + _sentryRewriteFramesDistDir?: string; SENTRY_RELEASE?: { id: string }; SENTRY_RELEASES?: { [key: string]: { id: string } }; }; diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 6b96b96ecec1..b83008a4817e 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -562,6 +562,8 @@ function setUpModuleRules(newConfig: WebpackConfigObject): WebpackConfigObjectWi /** * Adds loaders to inject values on the global object based on user configuration. */ +// TODO(v9): Remove this loader and replace it with a nextConfig.env (https://web.archive.org/web/20240917153554/https://nextjs.org/docs/app/api-reference/next-config-js/env) or define based (https://github.com/vercel/next.js/discussions/71476) approach. +// In order to remove this loader though we need to make sure the minimum supported Next.js version includes this PR (https://github.com/vercel/next.js/pull/61194), otherwise the nextConfig.env based approach will not work, as our SDK code is not processed by Next.js. function addValueInjectionLoader( newConfig: WebpackConfigObjectWithModuleRules, userNextConfig: NextConfigObject, @@ -572,7 +574,7 @@ function addValueInjectionLoader( const isomorphicValues = { // `rewritesTunnel` set by the user in Next.js config - __sentryRewritesTunnelPath__: + _sentryRewritesTunnelPath: userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export' ? `${userNextConfig.basePath ?? ''}${userSentryOptions.tunnelRoute}` : undefined, @@ -582,21 +584,21 @@ function addValueInjectionLoader( SENTRY_RELEASE: buildContext.dev ? undefined : { id: userSentryOptions.release?.name ?? getSentryRelease(buildContext.buildId) }, - __sentryBasePath: buildContext.dev ? userNextConfig.basePath : undefined, + _sentryBasePath: buildContext.dev ? userNextConfig.basePath : undefined, }; const serverValues = { ...isomorphicValues, // Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape // characters) - __rewriteFramesDistDir__: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next', + _sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next', }; const clientValues = { ...isomorphicValues, // Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if // `assetPrefix` doesn't include one. Since we only care about the path, it doesn't matter what it is.) - __rewriteFramesAssetPrefixPath__: assetPrefix + _sentryRewriteFramesAssetPrefixPath: assetPrefix ? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '') : '', }; diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index 4f5205fecfcb..539e75c20596 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -20,6 +20,7 @@ let showedExportModeTunnelWarning = false; * @param sentryBuildOptions Additional options to configure instrumentation and * @returns The modified config to be exported */ +// TODO(v9): Always return an async function here to allow us to do async things like grabbing a deterministic build ID. export function withSentryConfig(nextConfig?: C, sentryBuildOptions: SentryBuildOptions = {}): C { const castNextConfig = (nextConfig as NextConfig) || {}; if (typeof castNextConfig === 'function') { @@ -73,6 +74,8 @@ function getFinalConfigObject( } } + setUpBuildTimeVariables(incomingUserNextConfigObject, userSentryOptions); + const nextJsVersion = getNextjsVersion(); // Add the `clientTraceMetadata` experimental option based on Next.js version. The option got introduced in Next.js version 15.0.0 (actually 14.3.0-canary.64). @@ -253,6 +256,43 @@ function setUpTunnelRewriteRules(userNextConfig: NextConfigObject, tunnelPath: s }; } +// TODO(v9): Inject the release into all the bundles. This is breaking because grabbing the build ID if the user provides +// it in `generateBuildId` (https://nextjs.org/docs/app/api-reference/next-config-js/generateBuildId) is async but we do +// not turn the next config function in the type it was passed. +function setUpBuildTimeVariables(userNextConfig: NextConfigObject, userSentryOptions: SentryBuildOptions): void { + const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || ''; + const basePath = userNextConfig.basePath ?? ''; + const rewritesTunnelPath = + userSentryOptions.tunnelRoute !== undefined && userNextConfig.output !== 'export' + ? `${basePath}${userSentryOptions.tunnelRoute}` + : undefined; + + const buildTimeVariables: Record = { + // Make sure that if we have a windows path, the backslashes are interpreted as such (rather than as escape + // characters) + _sentryRewriteFramesDistDir: userNextConfig.distDir?.replace(/\\/g, '\\\\') || '.next', + // Get the path part of `assetPrefix`, minus any trailing slash. (We use a placeholder for the origin if + // `assetPrefix` doesn't include one. Since we only care about the path, it doesn't matter what it is.) + _sentryRewriteFramesAssetPrefixPath: assetPrefix + ? new URL(assetPrefix, 'http://dogs.are.great').pathname.replace(/\/$/, '') + : '', + }; + + if (rewritesTunnelPath) { + buildTimeVariables._sentryRewritesTunnelPath = rewritesTunnelPath; + } + + if (basePath) { + buildTimeVariables._sentryBasePath = basePath; + } + + if (typeof userNextConfig.env === 'object') { + userNextConfig.env = { ...buildTimeVariables, ...userNextConfig.env }; + } else if (userNextConfig.env === undefined) { + userNextConfig.env = buildTimeVariables; + } +} + function getNextjsVersion(): string | undefined { const nextjsPackageJsonPath = resolveNextjsPackageJson(); if (nextjsPackageJsonPath) { diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index fff4236bf3be..5bfc8cca054b 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -21,7 +21,7 @@ export { captureUnderscoreErrorException } from '../common/pages-router-instrume export type EdgeOptions = VercelEdgeOptions; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __rewriteFramesDistDir__?: string; + _sentryRewriteFramesDistDir?: string; }; /** Inits the Sentry NextJS SDK on the Edge Runtime. */ @@ -36,7 +36,7 @@ export function init(options: VercelEdgeOptions = {}): void { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir; if (distDirName) { customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName })); diff --git a/packages/nextjs/src/edge/rewriteFramesIntegration.ts b/packages/nextjs/src/edge/rewriteFramesIntegration.ts index 84d5a41b0922..15a541311ed1 100644 --- a/packages/nextjs/src/edge/rewriteFramesIntegration.ts +++ b/packages/nextjs/src/edge/rewriteFramesIntegration.ts @@ -3,7 +3,7 @@ import type { IntegrationFn, StackFrame } from '@sentry/types'; import { GLOBAL_OBJ, escapeStringForRegex } from '@sentry/utils'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __rewriteFramesDistDir__?: string; + _sentryRewriteFramesDistDir?: string; }; type StackFrameIteratee = (frame: StackFrame) => StackFrame; @@ -14,9 +14,8 @@ interface RewriteFramesOptions { } export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { - // This value is injected at build time, based on the output directory specified in the build config. Though a default - // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + // This value is injected at build time, based on the output directory specified in the build config. + const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir; if (distDirName) { const distDirAbsPath = distDirName.replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 7aade8cbd5c3..bdff0de922b2 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -42,8 +42,8 @@ export * from '@sentry/node'; export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { - __rewriteFramesDistDir__?: string; - __sentryRewritesTunnelPath__?: string; + _sentryRewriteFramesDistDir?: string; + _sentryRewritesTunnelPath?: string; }; /** @@ -109,7 +109,7 @@ export function init(options: NodeOptions): NodeClient | undefined { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir; if (distDirName) { customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName })); } @@ -212,8 +212,10 @@ export function init(options: NodeOptions): NodeClient | undefined { // Filter out transactions for requests to the tunnel route if ( - globalWithInjectedValues.__sentryRewritesTunnelPath__ && - event.transaction === `POST ${globalWithInjectedValues.__sentryRewritesTunnelPath__}` + (globalWithInjectedValues._sentryRewritesTunnelPath && + event.transaction === `POST ${globalWithInjectedValues._sentryRewritesTunnelPath}`) || + (process.env._sentryRewritesTunnelPath && + event.transaction === `POST ${process.env._sentryRewritesTunnelPath}`) ) { return null; } diff --git a/packages/nextjs/src/server/rewriteFramesIntegration.ts b/packages/nextjs/src/server/rewriteFramesIntegration.ts index 6438ccb0d922..33bc7d90cb99 100644 --- a/packages/nextjs/src/server/rewriteFramesIntegration.ts +++ b/packages/nextjs/src/server/rewriteFramesIntegration.ts @@ -4,7 +4,7 @@ import type { IntegrationFn, StackFrame } from '@sentry/types'; import { escapeStringForRegex } from '@sentry/utils'; const globalWithInjectedValues = global as typeof global & { - __rewriteFramesDistDir__?: string; + _sentryRewriteFramesDistDir?: string; }; type StackFrameIteratee = (frame: StackFrame) => StackFrame; @@ -17,7 +17,7 @@ interface RewriteFramesOptions { export const customRewriteFramesIntegration = ((options?: RewriteFramesOptions) => { // This value is injected at build time, based on the output directory specified in the build config. Though a default // is set there, we set it here as well, just in case something has gone wrong with the injection. - const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__; + const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir; if (distDirName) { // nextjs always puts the build directory at the project root level, which is also where you run `next start` from, so diff --git a/packages/nextjs/test/serverSdk.test.ts b/packages/nextjs/test/serverSdk.test.ts index 1129bcdbbf2b..fed88fe25d33 100644 --- a/packages/nextjs/test/serverSdk.test.ts +++ b/packages/nextjs/test/serverSdk.test.ts @@ -6,7 +6,7 @@ import { GLOBAL_OBJ } from '@sentry/utils'; import { init } from '../src/server'; // normally this is set as part of the build process, so mock it here -(GLOBAL_OBJ as typeof GLOBAL_OBJ & { __rewriteFramesDistDir__: string }).__rewriteFramesDistDir__ = '.next'; +(GLOBAL_OBJ as typeof GLOBAL_OBJ & { _sentryRewriteFramesDistDir: string })._sentryRewriteFramesDistDir = '.next'; const nodeInit = jest.spyOn(SentryNode, 'init'); diff --git a/packages/nextjs/test/utils/tunnelRoute.test.ts b/packages/nextjs/test/utils/tunnelRoute.test.ts index 576898c061b2..05aa992f39e6 100644 --- a/packages/nextjs/test/utils/tunnelRoute.test.ts +++ b/packages/nextjs/test/utils/tunnelRoute.test.ts @@ -3,16 +3,16 @@ import type { BrowserOptions } from '@sentry/react'; import { applyTunnelRouteOption } from '../../src/client/tunnelRoute'; const globalWithInjectedValues = global as typeof global & { - __sentryRewritesTunnelPath__?: string; + _sentryRewritesTunnelPath?: string; }; beforeEach(() => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = undefined; + globalWithInjectedValues._sentryRewritesTunnelPath = undefined; }); describe('applyTunnelRouteOption()', () => { it('Correctly applies `tunnelRoute` option when conditions are met', () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'https://11111111111111111111111111111111@o2222222.ingest.sentry.io/3333333', } as BrowserOptions; @@ -23,7 +23,7 @@ describe('applyTunnelRouteOption()', () => { }); it("Doesn't apply `tunnelRoute` when DSN is missing", () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { // no dsn } as BrowserOptions; @@ -34,7 +34,7 @@ describe('applyTunnelRouteOption()', () => { }); it("Doesn't apply `tunnelRoute` when DSN is invalid", () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'invalidDsn', } as BrowserOptions; @@ -55,7 +55,7 @@ describe('applyTunnelRouteOption()', () => { }); it("Doesn't `tunnelRoute` option when DSN is not a SaaS DSN", () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'https://11111111111111111111111111111111@example.com/3333333', } as BrowserOptions; @@ -66,7 +66,7 @@ describe('applyTunnelRouteOption()', () => { }); it('Correctly applies `tunnelRoute` option to region DSNs', () => { - globalWithInjectedValues.__sentryRewritesTunnelPath__ = '/my-error-monitoring-route'; + globalWithInjectedValues._sentryRewritesTunnelPath = '/my-error-monitoring-route'; const options: any = { dsn: 'https://11111111111111111111111111111111@o2222222.ingest.us.sentry.io/3333333', } as BrowserOptions; From b2605be3e53d242c1270d188ab338957e00fcccc Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 7 Nov 2024 03:10:00 +0100 Subject: [PATCH 04/24] feat(utils): Single implementation to fetch debug ids (#14199) In the process of fixing https://github.com/getsentry/sentry-electron/issues/1011 I was looking for a way to get a `Map`. I found that there was already some code duplication for parsing and caching of debug ids from the polyfilled globals. This PR moves the common code to `@sentry/utils` which will be useful for fixing the above. --- packages/browser/src/profiling/utils.ts | 67 ++--------------------- packages/core/src/utils/prepareEvent.ts | 51 ++++-------------- packages/profiling-node/src/utils.ts | 65 ++++------------------- packages/utils/src/debug-ids.ts | 70 +++++++++++++++++++++++++ packages/utils/src/index.ts | 1 + 5 files changed, 92 insertions(+), 162 deletions(-) create mode 100644 packages/utils/src/debug-ids.ts diff --git a/packages/browser/src/profiling/utils.ts b/packages/browser/src/profiling/utils.ts index 50272d9b5554..926ea6bbb9f5 100644 --- a/packages/browser/src/profiling/utils.ts +++ b/packages/browser/src/profiling/utils.ts @@ -1,21 +1,11 @@ /* eslint-disable max-lines */ import { DEFAULT_ENVIRONMENT, getClient, spanToJSON } from '@sentry/core'; -import type { - DebugImage, - Envelope, - Event, - EventEnvelope, - Profile, - Span, - StackFrame, - StackParser, - ThreadCpuProfile, -} from '@sentry/types'; +import type { DebugImage, Envelope, Event, EventEnvelope, Profile, Span, ThreadCpuProfile } from '@sentry/types'; import { - GLOBAL_OBJ, browserPerformanceTimeOrigin, forEachEnvelopeItem, + getDebugImagesForResources, logger, timestampInSeconds, uuid4, @@ -352,17 +342,10 @@ export function findProfiledTransactionsFromEnvelope(envelope: Envelope): Event[ return events; } -const debugIdStackParserCache = new WeakMap>(); /** * Applies debug meta data to an event from a list of paths to resources (sourcemaps) */ export function applyDebugMetadata(resource_paths: ReadonlyArray): DebugImage[] { - const debugIdMap = GLOBAL_OBJ._sentryDebugIds; - - if (!debugIdMap) { - return []; - } - const client = getClient(); const options = client && client.getOptions(); const stackParser = options && options.stackParser; @@ -371,51 +354,7 @@ export function applyDebugMetadata(resource_paths: ReadonlyArray): Debug return []; } - let debugIdStackFramesCache: Map; - const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser); - if (cachedDebugIdStackFrameCache) { - debugIdStackFramesCache = cachedDebugIdStackFrameCache; - } else { - debugIdStackFramesCache = new Map(); - debugIdStackParserCache.set(stackParser, debugIdStackFramesCache); - } - - // Build a map of filename -> debug_id - const filenameDebugIdMap = Object.keys(debugIdMap).reduce>((acc, debugIdStackTrace) => { - let parsedStack: StackFrame[]; - - const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace); - if (cachedParsedStack) { - parsedStack = cachedParsedStack; - } else { - parsedStack = stackParser(debugIdStackTrace); - debugIdStackFramesCache.set(debugIdStackTrace, parsedStack); - } - - for (let i = parsedStack.length - 1; i >= 0; i--) { - const stackFrame = parsedStack[i]; - const file = stackFrame && stackFrame.filename; - - if (stackFrame && file) { - acc[file] = debugIdMap[debugIdStackTrace] as string; - break; - } - } - return acc; - }, {}); - - const images: DebugImage[] = []; - for (const path of resource_paths) { - if (path && filenameDebugIdMap[path]) { - images.push({ - type: 'sourcemap', - code_file: path, - debug_id: filenameDebugIdMap[path] as string, - }); - } - } - - return images; + return getDebugImagesForResources(stackParser, resource_paths); } /** diff --git a/packages/core/src/utils/prepareEvent.ts b/packages/core/src/utils/prepareEvent.ts index f7f209a49089..9ee0a0f1b1b6 100644 --- a/packages/core/src/utils/prepareEvent.ts +++ b/packages/core/src/utils/prepareEvent.ts @@ -6,10 +6,16 @@ import type { EventHint, Scope as ScopeInterface, ScopeContext, - StackFrame, StackParser, } from '@sentry/types'; -import { GLOBAL_OBJ, addExceptionMechanism, dateTimestampInSeconds, normalize, truncate, uuid4 } from '@sentry/utils'; +import { + addExceptionMechanism, + dateTimestampInSeconds, + getFilenameToDebugIdMap, + normalize, + truncate, + uuid4, +} from '@sentry/utils'; import { DEFAULT_ENVIRONMENT } from '../constants'; import { getGlobalScope } from '../currentScopes'; @@ -161,51 +167,12 @@ function applyClientOptions(event: Event, options: ClientOptions): void { } } -const debugIdStackParserCache = new WeakMap>(); - /** * Puts debug IDs into the stack frames of an error event. */ export function applyDebugIds(event: Event, stackParser: StackParser): void { - const debugIdMap = GLOBAL_OBJ._sentryDebugIds; - - if (!debugIdMap) { - return; - } - - let debugIdStackFramesCache: Map; - const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser); - if (cachedDebugIdStackFrameCache) { - debugIdStackFramesCache = cachedDebugIdStackFrameCache; - } else { - debugIdStackFramesCache = new Map(); - debugIdStackParserCache.set(stackParser, debugIdStackFramesCache); - } - // Build a map of filename -> debug_id - const filenameDebugIdMap = Object.entries(debugIdMap).reduce>( - (acc, [debugIdStackTrace, debugIdValue]) => { - let parsedStack: StackFrame[]; - const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace); - if (cachedParsedStack) { - parsedStack = cachedParsedStack; - } else { - parsedStack = stackParser(debugIdStackTrace); - debugIdStackFramesCache.set(debugIdStackTrace, parsedStack); - } - - for (let i = parsedStack.length - 1; i >= 0; i--) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const stackFrame = parsedStack[i]!; - if (stackFrame.filename) { - acc[stackFrame.filename] = debugIdValue; - break; - } - } - return acc; - }, - {}, - ); + const filenameDebugIdMap = getFilenameToDebugIdMap(stackParser); try { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion diff --git a/packages/profiling-node/src/utils.ts b/packages/profiling-node/src/utils.ts index 1ec575aaaf3f..2693467d1f47 100644 --- a/packages/profiling-node/src/utils.ts +++ b/packages/profiling-node/src/utils.ts @@ -14,11 +14,16 @@ import type { ProfileChunkEnvelope, ProfileChunkItem, SdkInfo, - StackFrame, - StackParser, ThreadCpuProfile, } from '@sentry/types'; -import { GLOBAL_OBJ, createEnvelope, dsnToString, forEachEnvelopeItem, logger, uuid4 } from '@sentry/utils'; +import { + createEnvelope, + dsnToString, + forEachEnvelopeItem, + getDebugImagesForResources, + logger, + uuid4, +} from '@sentry/utils'; import { env, versions } from 'process'; import { isMainThread, threadId } from 'worker_threads'; @@ -415,69 +420,17 @@ export function makeProfileChunkEnvelope( ]); } -const debugIdStackParserCache = new WeakMap>(); - /** * Cross reference profile collected resources with debug_ids and return a list of debug images. * @param {string[]} resource_paths * @returns {DebugImage[]} */ export function applyDebugMetadata(client: Client, resource_paths: ReadonlyArray): DebugImage[] { - const debugIdMap = GLOBAL_OBJ._sentryDebugIds; - if (!debugIdMap) { - return []; - } - const options = client.getOptions(); if (!options || !options.stackParser) { return []; } - let debugIdStackFramesCache: Map; - const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(options.stackParser); - if (cachedDebugIdStackFrameCache) { - debugIdStackFramesCache = cachedDebugIdStackFrameCache; - } else { - debugIdStackFramesCache = new Map(); - debugIdStackParserCache.set(options.stackParser, debugIdStackFramesCache); - } - - // Build a map of filename -> debug_id. - const filenameDebugIdMap = Object.keys(debugIdMap).reduce>((acc, debugIdStackTrace) => { - let parsedStack: StackFrame[]; - - const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace); - if (cachedParsedStack) { - parsedStack = cachedParsedStack; - } else { - parsedStack = options.stackParser(debugIdStackTrace); - debugIdStackFramesCache.set(debugIdStackTrace, parsedStack); - } - - for (let i = parsedStack.length - 1; i >= 0; i--) { - const stackFrame = parsedStack[i]; - const file = stackFrame && stackFrame.filename; - - if (stackFrame && file) { - acc[file] = debugIdMap[debugIdStackTrace] as string; - break; - } - } - return acc; - }, {}); - - const images: DebugImage[] = []; - - for (const resource of resource_paths) { - if (resource && filenameDebugIdMap[resource]) { - images.push({ - type: 'sourcemap', - code_file: resource, - debug_id: filenameDebugIdMap[resource] as string, - }); - } - } - - return images; + return getDebugImagesForResources(options.stackParser, resource_paths); } diff --git a/packages/utils/src/debug-ids.ts b/packages/utils/src/debug-ids.ts new file mode 100644 index 000000000000..4802b9356965 --- /dev/null +++ b/packages/utils/src/debug-ids.ts @@ -0,0 +1,70 @@ +import type { DebugImage, StackFrame, StackParser } from '@sentry/types'; +import { GLOBAL_OBJ } from './worldwide'; + +const debugIdStackParserCache = new WeakMap>(); + +/** + * Returns a map of filenames to debug identifiers. + */ +export function getFilenameToDebugIdMap(stackParser: StackParser): Record { + const debugIdMap = GLOBAL_OBJ._sentryDebugIds; + if (!debugIdMap) { + return {}; + } + + let debugIdStackFramesCache: Map; + const cachedDebugIdStackFrameCache = debugIdStackParserCache.get(stackParser); + if (cachedDebugIdStackFrameCache) { + debugIdStackFramesCache = cachedDebugIdStackFrameCache; + } else { + debugIdStackFramesCache = new Map(); + debugIdStackParserCache.set(stackParser, debugIdStackFramesCache); + } + + // Build a map of filename -> debug_id. + return Object.keys(debugIdMap).reduce>((acc, debugIdStackTrace) => { + let parsedStack: StackFrame[]; + + const cachedParsedStack = debugIdStackFramesCache.get(debugIdStackTrace); + if (cachedParsedStack) { + parsedStack = cachedParsedStack; + } else { + parsedStack = stackParser(debugIdStackTrace); + debugIdStackFramesCache.set(debugIdStackTrace, parsedStack); + } + + for (let i = parsedStack.length - 1; i >= 0; i--) { + const stackFrame = parsedStack[i]; + const file = stackFrame && stackFrame.filename; + + if (stackFrame && file) { + acc[file] = debugIdMap[debugIdStackTrace] as string; + break; + } + } + return acc; + }, {}); +} + +/** + * Returns a list of debug images for the given resources. + */ +export function getDebugImagesForResources( + stackParser: StackParser, + resource_paths: ReadonlyArray, +): DebugImage[] { + const filenameDebugIdMap = getFilenameToDebugIdMap(stackParser); + + const images: DebugImage[] = []; + for (const path of resource_paths) { + if (path && filenameDebugIdMap[path]) { + images.push({ + type: 'sourcemap', + code_file: path, + debug_id: filenameDebugIdMap[path] as string, + }); + } + } + + return images; +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 4a2d68ca0d8b..2a89826313e8 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -40,3 +40,4 @@ export * from './buildPolyfills'; export * from './propagationContext'; export * from './vercelWaitUntil'; export * from './version'; +export * from './debug-ids'; From 39ad3bb58fdc17900f40128a38cc8f285546038d Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Thu, 7 Nov 2024 15:05:13 +0100 Subject: [PATCH 05/24] test(e2e): Fix react router 6 canary test (#14204) Resolves https://github.com/getsentry/sentry-javascript/issues/14200 RR was emitting an additional log message which caused the matching to fail. --- .../test-applications/react-router-6/tests/sse.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/tests/sse.test.ts b/dev-packages/e2e-tests/test-applications/react-router-6/tests/sse.test.ts index 24ae70449ced..b34fc8123899 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6/tests/sse.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-6/tests/sse.test.ts @@ -66,8 +66,12 @@ test('Waits for sse streaming when sse has been explicitly aborted', async ({ pa expect(resolveBodyDuration).toBe(0); // validate abort error was thrown by inspecting console - const consoleBreadcrumb = rootSpan.breadcrumbs?.find(breadcrumb => breadcrumb.category === 'console'); - expect(consoleBreadcrumb?.message).toBe('Could not fetch sse AbortError: BodyStreamBuffer was aborted'); + expect(rootSpan.breadcrumbs).toContainEqual( + expect.objectContaining({ + category: 'console', + message: 'Could not fetch sse AbortError: BodyStreamBuffer was aborted', + }), + ); }); test('Aborts when stream takes longer than 5s, by not updating the span duration', async ({ page }) => { From 8c2aa1b883785ab1ce5f7c8173907f5fcbf39912 Mon Sep 17 00:00:00 2001 From: Kaung Zin Hein <83657429+Zen-cronic@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:11:14 -0500 Subject: [PATCH 06/24] feat(node): Add `tedious` integration (#13486) Implement Tedious OTEL instrumentation in `packages/node`. Signed-off-by: Kaung Zin Hein Co-authored-by: Abhijeet Prasad --- .../node-integration-tests/package.json | 1 + .../suites/tracing/tedious/docker-compose.yml | 12 + .../suites/tracing/tedious/scenario.js | 65 ++++ .../suites/tracing/tedious/test.ts | 53 +++ packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/bun/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/package.json | 1 + packages/node/src/index.ts | 1 + .../node/src/integrations/tracing/index.ts | 3 + .../node/src/integrations/tracing/tedious.ts | 48 +++ packages/remix/src/index.server.ts | 1 + packages/solidstart/src/server/index.ts | 1 + packages/sveltekit/src/server/index.ts | 1 + yarn.lock | 349 +++++++++++++++++- 16 files changed, 536 insertions(+), 4 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/tedious/docker-compose.yml create mode 100644 dev-packages/node-integration-tests/suites/tracing/tedious/scenario.js create mode 100644 dev-packages/node-integration-tests/suites/tracing/tedious/test.ts create mode 100644 packages/node/src/integrations/tracing/tedious.ts diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 84d38e7aaf93..14c25b252f3f 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -65,6 +65,7 @@ "redis-4": "npm:redis@^4.6.14", "reflect-metadata": "0.2.1", "rxjs": "^7.8.1", + "tedious": "^18.6.1", "yargs": "^16.2.0" }, "devDependencies": { diff --git a/dev-packages/node-integration-tests/suites/tracing/tedious/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/tedious/docker-compose.yml new file mode 100644 index 000000000000..8e3604dca209 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/tedious/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3.9' + +services: + db: + image: mcr.microsoft.com/mssql/server:2022-latest + restart: always + container_name: integration-tests-tedious + ports: + - '1433:1433' + environment: + ACCEPT_EULA: 'Y' + MSSQL_SA_PASSWORD: 'TESTing123' diff --git a/dev-packages/node-integration-tests/suites/tracing/tedious/scenario.js b/dev-packages/node-integration-tests/suites/tracing/tedious/scenario.js new file mode 100644 index 000000000000..1a375cfb78e9 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/tedious/scenario.js @@ -0,0 +1,65 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); + +const { Connection, Request } = require('tedious'); + +const config = { + server: '127.0.0.1', + authentication: { + type: 'default', + options: { + userName: 'sa', + password: 'TESTing123', + }, + }, + options: { + port: 1433, + encrypt: false, + }, +}; + +const connection = new Connection(config); + +function executeAllStatements(span) { + executeStatement('SELECT 1 + 1 AS solution', () => { + executeStatement('SELECT GETDATE()', () => { + span.end(); + connection.close(); + }); + }); +} + +function executeStatement(query, callback) { + const request = new Request(query, err => { + if (err) { + throw err; + } + callback(); + }); + + connection.execSql(request); +} + +connection.connect(err => { + if (err) { + throw err; + } + + Sentry.startSpanManual( + { + op: 'transaction', + name: 'Test Transaction', + }, + span => { + // span must be ended manually after all queries + executeAllStatements(span); + }, + ); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/tedious/test.ts b/dev-packages/node-integration-tests/suites/tracing/tedious/test.ts new file mode 100644 index 000000000000..c4a0ae29fe38 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/tedious/test.ts @@ -0,0 +1,53 @@ +import { conditionalTest } from '../../../utils'; +import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; + +jest.setTimeout(75000); + +// Tedious version we are testing against only supports Node 18+ +// https://github.com/tediousjs/tedious/blob/8310c455a2cc1cba83c1ca3c16677da4f83e12a9/package.json#L38 +conditionalTest({ min: 18 })('tedious auto instrumentation', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + test('should auto-instrument `tedious` package', done => { + const EXPECTED_TRANSACTION = { + transaction: 'Test Transaction', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'SELECT GETDATE()', + data: expect.objectContaining({ + 'sentry.origin': 'auto.db.otel.tedious', + 'sentry.op': 'db', + 'db.name': 'master', + 'db.statement': 'SELECT GETDATE()', + 'db.system': 'mssql', + 'db.user': 'sa', + 'net.peer.name': '127.0.0.1', + 'net.peer.port': 1433, + }), + status: 'ok', + }), + expect.objectContaining({ + description: 'SELECT 1 + 1 AS solution', + data: expect.objectContaining({ + 'sentry.origin': 'auto.db.otel.tedious', + 'sentry.op': 'db', + 'db.name': 'master', + 'db.statement': 'SELECT 1 + 1 AS solution', + 'db.system': 'mssql', + 'db.user': 'sa', + 'net.peer.name': '127.0.0.1', + 'net.peer.port': 1433, + }), + status: 'ok', + }), + ]), + }; + + createRunner(__dirname, 'scenario.js') + .withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['1433'] }) + .expect({ transaction: EXPECTED_TRANSACTION }) + .start(done); + }); +}); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 27d83ce7980b..b2f57937148a 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -125,6 +125,7 @@ export { startSession, startSpan, startSpanManual, + tediousIntegration, trpcMiddleware, withActiveSpan, withIsolationScope, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index faa43522f2fc..e6a21a498e2e 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -99,6 +99,7 @@ export { mysqlIntegration, mysql2Integration, redisIntegration, + tediousIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 4d8e9fbb1310..246f6ca9049c 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -120,6 +120,7 @@ export { mysqlIntegration, mysql2Integration, redisIntegration, + tediousIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index 6bb4bbcd30a7..5efe98ac5474 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -99,6 +99,7 @@ export { mysqlIntegration, mysql2Integration, redisIntegration, + tediousIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/packages/node/package.json b/packages/node/package.json index 456f0c210929..a5b0d2abff21 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -90,6 +90,7 @@ "@opentelemetry/instrumentation-nestjs-core": "0.40.0", "@opentelemetry/instrumentation-pg": "0.44.0", "@opentelemetry/instrumentation-redis-4": "0.42.0", + "@opentelemetry/instrumentation-tedious": "0.15.0", "@opentelemetry/instrumentation-undici": "0.6.0", "@opentelemetry/resources": "^1.26.0", "@opentelemetry/sdk-trace-base": "^1.26.0", diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 4e1d1be22a8f..97694bee7c70 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -28,6 +28,7 @@ export { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/h export { koaIntegration, setupKoaErrorHandler } from './integrations/tracing/koa'; export { connectIntegration, setupConnectErrorHandler } from './integrations/tracing/connect'; export { spotlightIntegration } from './integrations/spotlight'; +export { tediousIntegration } from './integrations/tracing/tedious'; export { genericPoolIntegration } from './integrations/tracing/genericPool'; export { dataloaderIntegration } from './integrations/tracing/dataloader'; export { amqplibIntegration } from './integrations/tracing/amqplib'; diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index 328767c403be..1a1b8835011d 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -18,6 +18,7 @@ import { instrumentMysql2, mysql2Integration } from './mysql2'; import { instrumentNest, nestIntegration } from './nest/nest'; import { instrumentPostgres, postgresIntegration } from './postgres'; import { instrumentRedis, redisIntegration } from './redis'; +import { instrumentTedious, tediousIntegration } from './tedious'; /** * With OTEL, all performance integrations will be added, as OTEL only initializes them when the patched package is actually required. @@ -41,6 +42,7 @@ export function getAutoPerformanceIntegrations(): Integration[] { hapiIntegration(), koaIntegration(), connectIntegration(), + tediousIntegration(), genericPoolIntegration(), kafkaIntegration(), amqplibIntegration(), @@ -71,6 +73,7 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) => instrumentHapi, instrumentGraphql, instrumentRedis, + instrumentTedious, instrumentGenericPool, instrumentAmqplib, ]; diff --git a/packages/node/src/integrations/tracing/tedious.ts b/packages/node/src/integrations/tracing/tedious.ts new file mode 100644 index 000000000000..7cde3f69311d --- /dev/null +++ b/packages/node/src/integrations/tracing/tedious.ts @@ -0,0 +1,48 @@ +import { TediousInstrumentation } from '@opentelemetry/instrumentation-tedious'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; +import { generateInstrumentOnce } from '../../otel/instrument'; + +const TEDIUS_INSTRUMENTED_METHODS = new Set([ + 'callProcedure', + 'execSql', + 'execSqlBatch', + 'execBulkLoad', + 'prepare', + 'execute', +]); + +const INTEGRATION_NAME = 'Tedious'; + +export const instrumentTedious = generateInstrumentOnce(INTEGRATION_NAME, () => new TediousInstrumentation({})); + +const _tediousIntegration = (() => { + return { + name: INTEGRATION_NAME, + setupOnce() { + instrumentTedious(); + }, + + setup(client) { + client.on('spanStart', span => { + const { description, data } = spanToJSON(span); + // Tedius integration always set a span name and `db.system` attribute to `mssql`. + if (!description || data?.['db.system'] !== 'mssql') { + return; + } + + const operation = description?.split(' ')[0] || ''; + if (TEDIUS_INSTRUMENTED_METHODS.has(operation)) { + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.tedious'); + } + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Tedious integration + * + * Capture tracing data for tedious. + */ +export const tediousIntegration = defineIntegration(_tediousIntegration); diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index ffb5cf74983f..d0ee1082426d 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -124,6 +124,7 @@ export { startSession, startSpan, startSpanManual, + tediousIntegration, trpcMiddleware, withActiveSpan, withIsolationScope, diff --git a/packages/solidstart/src/server/index.ts b/packages/solidstart/src/server/index.ts index ccec95b916d3..21705110361b 100644 --- a/packages/solidstart/src/server/index.ts +++ b/packages/solidstart/src/server/index.ts @@ -115,6 +115,7 @@ export { startSession, startSpan, startSpanManual, + tediousIntegration, trpcMiddleware, withActiveSpan, withIsolationScope, diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 728616945286..bf317b964191 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -117,6 +117,7 @@ export { startSession, startSpan, startSpanManual, + tediousIntegration, trpcMiddleware, withActiveSpan, withIsolationScope, diff --git a/yarn.lock b/yarn.lock index 652c5721f5b6..e8474dd23fbe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1059,6 +1059,179 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" +"@azure/abort-controller@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249" + integrity sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw== + dependencies: + tslib "^2.2.0" + +"@azure/abort-controller@^2.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-2.1.2.tgz#42fe0ccab23841d9905812c58f1082d27784566d" + integrity sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA== + dependencies: + tslib "^2.6.2" + +"@azure/core-auth@^1.3.0", "@azure/core-auth@^1.4.0": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.7.2.tgz#558b7cb7dd12b00beec07ae5df5907d74df1ebd9" + integrity sha512-Igm/S3fDYmnMq1uKS38Ae1/m37B3zigdlZw+kocwEhh5GjyKjPrXKO2J6rzpC1wAxrNil/jX9BJRqBshyjnF3g== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-util" "^1.1.0" + tslib "^2.6.2" + +"@azure/core-auth@^1.5.0", "@azure/core-auth@^1.7.2": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.8.0.tgz#281b4a6d3309c3e7b15bcd967f01d4c79ae4a1d6" + integrity sha512-YvFMowkXzLbXNM11yZtVLhUCmuG0ex7JKOH366ipjmHBhL3vpDcPAeWF+jf0X+jVXwFqo3UhsWUq4kH0ZPdu/g== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-util" "^1.1.0" + tslib "^2.6.2" + +"@azure/core-client@^1.3.0", "@azure/core-client@^1.5.0", "@azure/core-client@^1.9.2": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@azure/core-client/-/core-client-1.9.2.tgz#6fc69cee2816883ab6c5cdd653ee4f2ff9774f74" + integrity sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-auth" "^1.4.0" + "@azure/core-rest-pipeline" "^1.9.1" + "@azure/core-tracing" "^1.0.0" + "@azure/core-util" "^1.6.1" + "@azure/logger" "^1.0.0" + tslib "^2.6.2" + +"@azure/core-http-compat@^2.0.1": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@azure/core-http-compat/-/core-http-compat-2.1.2.tgz#d1585ada24ba750dc161d816169b33b35f762f0d" + integrity sha512-5MnV1yqzZwgNLLjlizsU3QqOeQChkIXw781Fwh1xdAqJR5AA32IUaq6xv1BICJvfbHoa+JYcaij2HFkhLbNTJQ== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-client" "^1.3.0" + "@azure/core-rest-pipeline" "^1.3.0" + +"@azure/core-lro@^2.2.0": + version "2.7.2" + resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-2.7.2.tgz#787105027a20e45c77651a98b01a4d3b01b75a08" + integrity sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-util" "^1.2.0" + "@azure/logger" "^1.0.0" + tslib "^2.6.2" + +"@azure/core-paging@^1.1.1": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@azure/core-paging/-/core-paging-1.6.2.tgz#40d3860dc2df7f291d66350b2cfd9171526433e7" + integrity sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA== + dependencies: + tslib "^2.6.2" + +"@azure/core-rest-pipeline@^1.1.0", "@azure/core-rest-pipeline@^1.3.0", "@azure/core-rest-pipeline@^1.8.1", "@azure/core-rest-pipeline@^1.9.1": + version "1.16.3" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.16.3.tgz#bde3bc3ebad7f885ddd9de6af5e5a8fc254b287e" + integrity sha512-VxLk4AHLyqcHsfKe4MZ6IQ+D+ShuByy+RfStKfSjxJoL3WBWq17VNmrz8aT8etKzqc2nAeIyLxScjpzsS4fz8w== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-auth" "^1.4.0" + "@azure/core-tracing" "^1.0.1" + "@azure/core-util" "^1.9.0" + "@azure/logger" "^1.0.0" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.0" + tslib "^2.6.2" + +"@azure/core-tracing@^1.0.0", "@azure/core-tracing@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.1.2.tgz#065dab4e093fb61899988a1cdbc827d9ad90b4ee" + integrity sha512-dawW9ifvWAWmUm9/h+/UQ2jrdvjCJ7VJEuCJ6XVNudzcOwm53BFZH4Q845vjfgoUAM8ZxokvVNxNxAITc502YA== + dependencies: + tslib "^2.6.2" + +"@azure/core-util@^1.0.0", "@azure/core-util@^1.1.0", "@azure/core-util@^1.2.0", "@azure/core-util@^1.6.1", "@azure/core-util@^1.9.0": + version "1.9.2" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.9.2.tgz#1dc37dc5b0dae34c578be62cf98905ba7c0cafe7" + integrity sha512-l1Qrqhi4x1aekkV+OlcqsJa4AnAkj5p0JV8omgwjaV9OAbP41lvrMvs+CptfetKkeEaGRGSzby7sjPZEX7+kkQ== + dependencies: + "@azure/abort-controller" "^2.0.0" + tslib "^2.6.2" + +"@azure/core-util@^1.3.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.10.0.tgz#cf3163382d40343972848c914869864df5d44bdb" + integrity sha512-dqLWQsh9Nro1YQU+405POVtXnwrIVqPyfUzc4zXCbThTg7+vNNaiMkwbX9AMXKyoFYFClxmB3s25ZFr3+jZkww== + dependencies: + "@azure/abort-controller" "^2.0.0" + tslib "^2.6.2" + +"@azure/identity@^4.2.1": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-4.4.1.tgz#490fa2ad26786229afa36411892bb53dfa3478d3" + integrity sha512-DwnG4cKFEM7S3T+9u05NstXU/HN0dk45kPOinUyNKsn5VWwpXd9sbPKEg6kgJzGbm1lMuhx9o31PVbCtM5sfBA== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.5.0" + "@azure/core-client" "^1.9.2" + "@azure/core-rest-pipeline" "^1.1.0" + "@azure/core-tracing" "^1.0.0" + "@azure/core-util" "^1.3.0" + "@azure/logger" "^1.0.0" + "@azure/msal-browser" "^3.14.0" + "@azure/msal-node" "^2.9.2" + events "^3.0.0" + jws "^4.0.0" + open "^8.0.0" + stoppable "^1.1.0" + tslib "^2.2.0" + +"@azure/keyvault-keys@^4.4.0": + version "4.8.0" + resolved "https://registry.yarnpkg.com/@azure/keyvault-keys/-/keyvault-keys-4.8.0.tgz#1513b3a187bb3a9a372b5980c593962fb793b2ad" + integrity sha512-jkuYxgkw0aaRfk40OQhFqDIupqblIOIlYESWB6DKCVDxQet1pyv86Tfk9M+5uFM0+mCs6+MUHU+Hxh3joiUn4Q== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.3.0" + "@azure/core-client" "^1.5.0" + "@azure/core-http-compat" "^2.0.1" + "@azure/core-lro" "^2.2.0" + "@azure/core-paging" "^1.1.1" + "@azure/core-rest-pipeline" "^1.8.1" + "@azure/core-tracing" "^1.0.0" + "@azure/core-util" "^1.0.0" + "@azure/logger" "^1.0.0" + tslib "^2.2.0" + +"@azure/logger@^1.0.0": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.1.4.tgz#223cbf2b424dfa66478ce9a4f575f59c6f379768" + integrity sha512-4IXXzcCdLdlXuCG+8UKEwLA1T1NHqUfanhXYHiQTn+6sfWCZXduqbtXDGceg3Ce5QxTGo7EqmbV6Bi+aqKuClQ== + dependencies: + tslib "^2.6.2" + +"@azure/msal-browser@^3.14.0": + version "3.25.0" + resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-3.25.0.tgz#7ce0949977bc9e0c58319f7090c44fe5537104d4" + integrity sha512-a0Y7pmSy8SC1s9bvwr+REvyAA1nQcITlZvkElM2gNUPYFTTNUTEdcpg73TmawNucyMdZ9xb/GFcuhrLOqYAzwg== + dependencies: + "@azure/msal-common" "14.15.0" + +"@azure/msal-common@14.15.0": + version "14.15.0" + resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.15.0.tgz#0e27ac0bb88fe100f4f8d1605b64d5c268636a55" + integrity sha512-ImAQHxmpMneJ/4S8BRFhjt1MZ3bppmpRPYYNyzeQPeFN288YKbb8TmmISQEbtfkQ1BPASvYZU5doIZOPBAqENQ== + +"@azure/msal-node@^2.9.2": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.15.0.tgz#50bf8e692a6656027c073a75d877a8a478aafdfd" + integrity sha512-gVPW8YLz92ZeCibQH2QUw96odJoiM3k/ZPH3f2HxptozmH6+OnyyvKXo/Egg39HAM230akarQKHf0W74UHlh0Q== + dependencies: + "@azure/msal-common" "14.15.0" + jsonwebtoken "^9.0.0" + uuid "^8.3.0" + "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -6367,6 +6540,11 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@js-joda/core@^5.6.1": + version "5.6.3" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-5.6.3.tgz#41ae1c07de1ebe0f6dde1abcbc9700a09b9c6056" + integrity sha512-T1rRxzdqkEXcou0ZprN1q9yDRlvzCPLqmlNt5IIsGBzoEVgLCCYrKEwc84+TvsXuAc95VAZwtWD2zVsKPY4bcA== + "@kwsites/file-exists@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" @@ -7504,6 +7682,15 @@ "@opentelemetry/redis-common" "^0.36.2" "@opentelemetry/semantic-conventions" "^1.27.0" +"@opentelemetry/instrumentation-tedious@0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.15.0.tgz#da82f4d153fb6ff7d1f85d39872ac40bf9db12ea" + integrity sha512-Kb7yo8Zsq2TUwBbmwYgTAMPK0VbhoS8ikJ6Bup9KrDtCx2JC01nCb+M0VJWXt7tl0+5jARUbKWh5jRSoImxdCw== + dependencies: + "@opentelemetry/instrumentation" "^0.54.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@types/tedious" "^4.0.14" + "@opentelemetry/instrumentation-undici@0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.6.0.tgz#9436ee155c8dcb0b760b66947c0e0f347688a5ef" @@ -9913,6 +10100,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.2.tgz#d76fb80d87d0d8abfe334fc6d292e83e5524efc4" integrity sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w== +"@types/node@>=18": + version "22.7.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.4.tgz#e35d6f48dca3255ce44256ddc05dee1c23353fcc" + integrity sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg== + dependencies: + undici-types "~6.19.2" + "@types/node@^10.1.0": version "10.17.60" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" @@ -10040,6 +10234,14 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/readable-stream@^4.0.0": + version "4.0.15" + resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-4.0.15.tgz#e6ec26fe5b02f578c60baf1fa9452e90957d2bfb" + integrity sha512-oAZ3kw+kJFkEqyh7xORZOku1YAKvsFTogRY8kVl4vHpEKiDkfnSA/My8haRE7fvmix5Zyy+1pwzOi7yycGLBJw== + dependencies: + "@types/node" "*" + safe-buffer "~5.1.1" + "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" @@ -10161,6 +10363,13 @@ resolved "https://registry.yarnpkg.com/@types/symlink-or-copy/-/symlink-or-copy-1.2.0.tgz#4151a81b4052c80bc2becbae09f3a9ec010a9c7a" integrity sha512-Lja2xYuuf2B3knEsga8ShbOdsfNOtzT73GyJmZyY7eGl2+ajOqrs8yM5ze0fsSoYwvA6bw7/Qr7OZ7PEEmYwWg== +"@types/tedious@^4.0.14": + version "4.0.14" + resolved "https://registry.yarnpkg.com/@types/tedious/-/tedious-4.0.14.tgz#868118e7a67808258c05158e9cad89ca58a2aec1" + integrity sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw== + dependencies: + "@types/node" "*" + "@types/tmp@^0.2.2": version "0.2.3" resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.3.tgz#908bfb113419fd6a42273674c00994d40902c165" @@ -11338,6 +11547,13 @@ agent-base@6, agent-base@^6.0.2: dependencies: debug "4" +agent-base@^7.0.2, agent-base@^7.1.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== + dependencies: + debug "^4.3.4" + agentkeepalive@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" @@ -12700,6 +12916,16 @@ bl@^5.0.0: inherits "^2.0.4" readable-stream "^3.4.0" +bl@^6.0.11: + version "6.0.16" + resolved "https://registry.yarnpkg.com/bl/-/bl-6.0.16.tgz#29b190f1a754e2d168de3dc8c74ed8d12bf78e6e" + integrity sha512-V/kz+z2Mx5/6qDfRCilmrukUXcXuCoXKg3/3hDvzKKoSUx8CJKudfIoT29XZc3UE9xBvxs5qictiHdprwtteEg== + dependencies: + "@types/readable-stream" "^4.0.0" + buffer "^6.0.3" + inherits "^2.0.4" + readable-stream "^4.2.0" + blake3-wasm@^2.1.5: version "2.1.5" resolved "https://registry.yarnpkg.com/blake3-wasm/-/blake3-wasm-2.1.5.tgz#b22dbb84bc9419ed0159caa76af4b1b132e6ba52" @@ -17764,7 +17990,7 @@ events-to-array@^1.0.1: resolved "https://registry.yarnpkg.com/events-to-array/-/events-to-array-1.1.2.tgz#2d41f563e1fe400ed4962fe1a4d5c6a7539df7f6" integrity sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y= -events@^3.2.0, events@^3.3.0: +events@^3.0.0, events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -20166,6 +20392,14 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +http-proxy-agent@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + http-proxy-middleware@^2.0.3: version "2.0.7" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6" @@ -20209,6 +20443,14 @@ https-proxy-agent@5.0.1, https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: agent-base "6" debug "4" +https-proxy-agent@^7.0.0: + version "7.0.5" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" + integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== + dependencies: + agent-base "^7.0.2" + debug "4" + https@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https/-/https-1.0.0.tgz#3c37c7ae1a8eeb966904a2ad1e975a194b7ed3a4" @@ -21885,6 +22127,11 @@ js-cleanup@^1.2.0: perf-regexes "^1.0.1" skip-regex "^1.0.2" +js-md4@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/js-md4/-/js-md4-0.3.2.tgz#cd3b3dc045b0c404556c81ddb5756c23e59d7cf5" + integrity sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA== + js-string-escape@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" @@ -22142,6 +22389,22 @@ jsonparse@^1.2.0, jsonparse@^1.3.1: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== +jsonwebtoken@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.2.0" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82" @@ -22155,6 +22418,15 @@ just-extend@^6.2.0: resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-6.2.0.tgz#b816abfb3d67ee860482e7401564672558163947" integrity sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw== +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + jwa@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" @@ -22164,6 +22436,14 @@ jwa@^2.0.0: ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + jws@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" @@ -22762,6 +23042,11 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + lodash.isarguments@^3.0.0, lodash.isarguments@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -22772,11 +23057,36 @@ lodash.isarray@^3.0.0: resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc= +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.kebabcase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" @@ -22806,6 +23116,11 @@ lodash.omit@^4.1.0: resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA= +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" @@ -24601,6 +24916,11 @@ napi-wasm@^1.1.0: resolved "https://registry.yarnpkg.com/napi-wasm/-/napi-wasm-1.1.0.tgz#bbe617823765ae9c1bc12ff5942370eae7b2ba4e" integrity sha512-lHwIAJbmLSjF9VDRm9GoVOy9AGp3aIvkjv+Kvz9h16QR3uSVYH78PNQUnT2U4X53mhlnV2M7wrhibQ3GHicDmg== +native-duplexpair@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/native-duplexpair/-/native-duplexpair-1.0.0.tgz#7899078e64bf3c8a3d732601b3d40ff05db58fa0" + integrity sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA== + natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -25750,7 +26070,7 @@ open@^10.1.0: is-inside-container "^1.0.0" is-wsl "^3.1.0" -open@^8.0.9: +open@^8.0.0, open@^8.0.9: version "8.4.2" resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== @@ -28344,7 +28664,7 @@ readable-stream@^2.0.5: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^4.0.0: +readable-stream@^4.0.0, readable-stream@^4.2.0: version "4.5.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== @@ -31156,6 +31476,22 @@ tar@^6.2.0: mkdirp "^1.0.3" yallist "^4.0.0" +tedious@^18.6.1: + version "18.6.1" + resolved "https://registry.yarnpkg.com/tedious/-/tedious-18.6.1.tgz#1c4a3f06c891be67a032117e2e25193286d44496" + integrity sha512-9AvErXXQTd6l7TDd5EmM+nxbOGyhnmdbp/8c3pw+tjaiSXW9usME90ET/CRG1LN1Y9tPMtz/p83z4Q97B4DDpw== + dependencies: + "@azure/core-auth" "^1.7.2" + "@azure/identity" "^4.2.1" + "@azure/keyvault-keys" "^4.4.0" + "@js-joda/core" "^5.6.1" + "@types/node" ">=18" + bl "^6.0.11" + iconv-lite "^0.6.3" + js-md4 "^0.3.2" + native-duplexpair "^1.0.0" + sprintf-js "^1.1.3" + teeny-request@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-7.0.1.tgz#bdd41fdffea5f8fbc0d29392cb47bec4f66b2b4c" @@ -32025,6 +32361,11 @@ underscore@>=1.8.3: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.1.tgz#7bb8cc9b3d397e201cf8553336d262544ead829e" integrity sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + undici@^5.25.4: version "5.28.3" resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.3.tgz#a731e0eff2c3fcfd41c1169a869062be222d1e5b" @@ -32651,7 +32992,7 @@ uuid-v4@^0.1.0: resolved "https://registry.yarnpkg.com/uuid-v4/-/uuid-v4-0.1.0.tgz#62d7b310406f6cecfea1528c69f1e8e0bcec5a3a" integrity sha512-m11RYDtowtAIihBXMoGajOEKpAXrKbpKlpmxqyztMYQNGSY5nZAZ/oYch/w2HNS1RMA4WLGcZvuD8/wFMuCEzA== -uuid@8.3.2, uuid@^8.0.0, uuid@^8.3.1, uuid@^8.3.2: +uuid@8.3.2, uuid@^8.0.0, uuid@^8.3.0, uuid@^8.3.1, uuid@^8.3.2: version "8.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== From cb1f001bb021d36b4f3f70a668248a9040882703 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Fri, 8 Nov 2024 13:13:34 +0000 Subject: [PATCH 07/24] fix(spotlight): Export spotlightBrowserIntegration from the main browser package (#14208) Follow up to #13263 where we forgot to export it from the main package :facepalm: --- packages/browser/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 10b974dc35e8..56724b926c5b 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -74,3 +74,4 @@ export { export type { Span } from '@sentry/types'; export { makeBrowserOfflineTransport } from './transports/offline'; export { browserProfilingIntegration } from './profiling/integration'; +export { spotlightBrowserIntegration } from './integrations/spotlight'; From a91a5ba70f36a51c486cd9acee24b1b1838ffa93 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 8 Nov 2024 14:49:05 +0100 Subject: [PATCH 08/24] test(e2e): Pin react-router-dom dep in `react-send-to-sentry` Test (#14213) Causes failures on develop --- .../test-applications/react-send-to-sentry/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json index 4684e3401e63..95b9c3bd78b4 100644 --- a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/package.json @@ -9,7 +9,7 @@ "@types/react-dom": "18.0.0", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "^6.4.1", + "react-router-dom": "6.4.1", "react-scripts": "5.0.1", "typescript": "4.9.5" }, From b9e85c1a733f04b471450ed797fff9d2fa8617a6 Mon Sep 17 00:00:00 2001 From: Gili Shohat <40733156+gilisho@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:09:41 +0200 Subject: [PATCH 09/24] feat(browser): Add moduleMetadataIntegration lazy loading support (#13817) This PR fixes #13803, and adds support for `moduleMetadataIntegration` to be lazy loaded, in [this](https://docs.sentry.io/platforms/javascript/configuration/integrations/#2-load-from-cdn-with-lazyloadintegration) manner. This integration is crucial for the [micro-frontend recommended solution](https://docs.sentry.io/platforms/javascript/best-practices/micro-frontends/#automatically-route-errors-to-different-projects-depending-on-module). --- .../moduleMetadataIntegration/init.js | 12 +++++++ .../moduleMetadataIntegration/subject.js | 7 ++++ .../moduleMetadataIntegration/test.ts | 33 +++++++++++++++++++ .../utils/generatePlugin.ts | 1 + packages/browser/rollup.bundle.config.mjs | 1 + .../index.modulemetadata.ts | 1 + .../browser/src/utils/lazyLoadIntegration.ts | 1 + 7 files changed, 56 insertions(+) create mode 100644 dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/init.js create mode 100644 dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/test.ts create mode 100644 packages/browser/src/integrations-bundle/index.modulemetadata.ts diff --git a/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/init.js b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/init.js new file mode 100644 index 000000000000..5991109fce66 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/init.js @@ -0,0 +1,12 @@ +import * as Sentry from '@sentry/browser'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [], +}); + +window.Sentry = { + ...Sentry, + // Ensure this is _not_ set + moduleMetadataIntegration: undefined, +}; diff --git a/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/subject.js b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/subject.js new file mode 100644 index 000000000000..312b4cf91f10 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/subject.js @@ -0,0 +1,7 @@ +window._testLazyLoadIntegration = async function run() { + const integration = await window.Sentry.lazyLoadIntegration('moduleMetadataIntegration'); + + window.Sentry.getClient()?.addIntegration(integration()); + + window._integrationLoaded = true; +}; diff --git a/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/test.ts b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/test.ts new file mode 100644 index 000000000000..ac8465736743 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/lazyLoad/moduleMetadataIntegration/test.ts @@ -0,0 +1,33 @@ +import { expect } from '@playwright/test'; +import { SDK_VERSION } from '@sentry/browser'; + +import { sentryTest } from '../../../../utils/fixtures'; + +sentryTest('it allows to lazy load the moduleMetadata integration', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route(`https://browser.sentry-cdn.com/${SDK_VERSION}/modulemetadata.min.js`, route => { + return route.fulfill({ + status: 200, + contentType: 'application/javascript;', + body: "window.Sentry.moduleMetadataIntegration = () => ({ name: 'ModuleMetadata' })", + }); + }); + + await page.goto(url); + + const hasIntegration = await page.evaluate('!!window.Sentry.getClient()?.getIntegrationByName("ModuleMetadata")'); + expect(hasIntegration).toBe(false); + + const scriptTagsBefore = await page.evaluate('document.querySelectorAll("script").length'); + + await page.evaluate('window._testLazyLoadIntegration()'); + await page.waitForFunction('window._integrationLoaded'); + + const scriptTagsAfter = await page.evaluate('document.querySelectorAll("script").length'); + + const hasIntegration2 = await page.evaluate('!!window.Sentry.getClient()?.getIntegrationByName("ModuleMetadata")'); + expect(hasIntegration2).toBe(true); + + expect(scriptTagsAfter).toBe(scriptTagsBefore + 1); +}); diff --git a/dev-packages/browser-integration-tests/utils/generatePlugin.ts b/dev-packages/browser-integration-tests/utils/generatePlugin.ts index acc583506df4..26a086bf2a77 100644 --- a/dev-packages/browser-integration-tests/utils/generatePlugin.ts +++ b/dev-packages/browser-integration-tests/utils/generatePlugin.ts @@ -37,6 +37,7 @@ const IMPORTED_INTEGRATION_CDN_BUNDLE_PATHS: Record = { reportingObserverIntegration: 'reportingobserver', sessionTimingIntegration: 'sessiontiming', feedbackIntegration: 'feedback', + moduleMetadataIntegration: 'modulemetadata', }; const BUNDLE_PATHS: Record> = { diff --git a/packages/browser/rollup.bundle.config.mjs b/packages/browser/rollup.bundle.config.mjs index 6a3c6342842b..f65c27aad6e9 100644 --- a/packages/browser/rollup.bundle.config.mjs +++ b/packages/browser/rollup.bundle.config.mjs @@ -12,6 +12,7 @@ const reexportedPluggableIntegrationFiles = [ 'rewriteframes', 'sessiontiming', 'feedback', + 'modulemetadata', ]; browserPluggableIntegrationFiles.forEach(integrationName => { diff --git a/packages/browser/src/integrations-bundle/index.modulemetadata.ts b/packages/browser/src/integrations-bundle/index.modulemetadata.ts new file mode 100644 index 000000000000..c4f4a2b9cf75 --- /dev/null +++ b/packages/browser/src/integrations-bundle/index.modulemetadata.ts @@ -0,0 +1 @@ +export { moduleMetadataIntegration } from '@sentry/core'; diff --git a/packages/browser/src/utils/lazyLoadIntegration.ts b/packages/browser/src/utils/lazyLoadIntegration.ts index 82260ae9724f..5b78f646c577 100644 --- a/packages/browser/src/utils/lazyLoadIntegration.ts +++ b/packages/browser/src/utils/lazyLoadIntegration.ts @@ -21,6 +21,7 @@ const LazyLoadableIntegrations = { rewriteFramesIntegration: 'rewriteframes', sessionTimingIntegration: 'sessiontiming', browserProfilingIntegration: 'browserprofiling', + moduleMetadataIntegration: 'modulemetadata', } as const; const WindowWithMaybeIntegration = WINDOW as { From e9d8ec82eb9cb4b29d3d591b7ffbd45a509143fc Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 11 Nov 2024 10:29:35 +0100 Subject: [PATCH 10/24] ref: Add external contributor to CHANGELOG.md (#14219) This PR adds the external contributor to the CHANGELOG.md file, so that they are credited for their contribution. See #13817 Co-authored-by: mydea <2411343+mydea@users.noreply.github.com> --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58035fb653c2..4a416a63eed4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +Work in this release was contributed by @gilisho. Thank you for your contribution! + ## 8.37.1 - feat(deps): Bump @opentelemetry/instrumentation from 0.53.0 to 0.54.0 for @sentry/opentelemetry ([#14187](https://github.com/getsentry/sentry-javascript/pull/14187)) From a579fbaae0fb21ccf14a2a077a2eec5b1ae9ce98 Mon Sep 17 00:00:00 2001 From: Minh-Phuc Tran Date: Mon, 11 Nov 2024 16:30:00 +0700 Subject: [PATCH 11/24] feat(core): Add trpc path to context in trpcMiddleware (#14218) Resolves https://github.com/getsentry/sentry-javascript/issues/14158 Before submitting a pull request, please take a look at our [Contributing](https://github.com/getsentry/sentry-javascript/blob/master/CONTRIBUTING.md) guidelines and verify: - [x] If you've added code that should be tested, please add tests. - [x] Ensure your code lints and the test suite passes (`yarn lint`) & (`yarn test`). --------- Co-authored-by: Luca Forstner --- .../nextjs-t3/tests/trpc-error.test.ts | 11 ++-- .../nextjs-t3/tests/trpc-mutation.test.ts | 3 -- .../node-express/tests/trpc.test.ts | 12 ++--- packages/core/src/trpc.ts | 51 ++++++++++--------- 4 files changed, 34 insertions(+), 43 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-error.test.ts index 0245b641db5c..d3e175e0558b 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-error.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-error.test.ts @@ -12,9 +12,10 @@ test('should capture error with trpc context', async ({ page }) => { const trpcError = await errorEventPromise; expect(trpcError).toBeDefined(); - expect(trpcError.contexts.trpc).toBeDefined(); - expect(trpcError.contexts.trpc.procedure_type).toEqual('mutation'); - expect(trpcError.contexts.trpc.input).toEqual({ name: 'I love dogs' }); + expect(trpcError.contexts?.trpc).toBeDefined(); + expect(trpcError.contexts?.trpc?.procedure_type).toEqual('mutation'); + expect(trpcError.contexts?.trpc?.procedure_path).toBe('post.throwError'); + expect(trpcError.contexts?.trpc?.input).toEqual({ name: 'I love dogs' }); }); test('should create transaction with trpc input for error', async ({ page }) => { @@ -26,9 +27,5 @@ test('should create transaction with trpc input for error', async ({ page }) => await page.click('#error-button'); const trpcTransaction = await trpcTransactionPromise; - expect(trpcTransaction).toBeDefined(); - expect(trpcTransaction.contexts.trpc).toBeDefined(); - expect(trpcTransaction.contexts.trpc.procedure_type).toEqual('mutation'); - expect(trpcTransaction.contexts.trpc.input).toEqual({ name: 'I love dogs' }); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-mutation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-mutation.test.ts index 47d6a52f8a19..ee3ebfd099ff 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-mutation.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/tests/trpc-mutation.test.ts @@ -13,7 +13,4 @@ test('should create transaction with trpc input for mutation', async ({ page }) const trpcTransaction = await trpcTransactionPromise; expect(trpcTransaction).toBeDefined(); - expect(trpcTransaction.contexts.trpc).toBeDefined(); - expect(trpcTransaction.contexts.trpc.procedure_type).toEqual('mutation'); - expect(trpcTransaction.contexts.trpc.input).toEqual({ name: 'I love dogs' }); }); diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts index 4f274fdc16ae..fcdd9b39a103 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/trpc.test.ts @@ -33,11 +33,6 @@ test('Should record span for trpc query', async ({ baseURL }) => { description: `trpc/getSomething`, }), ); - - expect(transaction.contexts?.trpc).toMatchObject({ - procedure_type: 'query', - input: 'foobar', - }); }); test('Should record transaction for trpc mutation', async ({ baseURL }) => { @@ -70,10 +65,6 @@ test('Should record transaction for trpc mutation', async ({ baseURL }) => { description: `trpc/createSomething`, }), ); - - expect(transaction.contexts?.trpc).toMatchObject({ - procedure_type: 'mutation', - }); }); test('Should record transaction and error for a crashing trpc handler', async ({ baseURL }) => { @@ -100,6 +91,9 @@ test('Should record transaction and error for a crashing trpc handler', async ({ await expect(transactionEventPromise).resolves.toBeDefined(); await expect(errorEventPromise).resolves.toBeDefined(); + + expect((await errorEventPromise).contexts?.trpc?.['procedure_type']).toBe('mutation'); + expect((await errorEventPromise).contexts?.trpc?.['procedure_path']).toBe('crashSomething'); }); test('Should record transaction and error for a trpc handler that returns a status code', async ({ baseURL }) => { diff --git a/packages/core/src/trpc.ts b/packages/core/src/trpc.ts index 366a0ba9aa62..fbcdf5832b46 100644 --- a/packages/core/src/trpc.ts +++ b/packages/core/src/trpc.ts @@ -1,7 +1,7 @@ import { normalize } from '@sentry/utils'; -import { getClient } from './currentScopes'; -import { captureException, setContext } from './exports'; +import { getClient, withScope } from './currentScopes'; +import { captureException } from './exports'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from './semanticAttributes'; import { startSpanManual } from './tracing'; @@ -48,6 +48,7 @@ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { const clientOptions = client && client.getOptions(); const trpcContext: Record = { + procedure_path: path, procedure_type: type, }; @@ -66,29 +67,31 @@ export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { } } } - setContext('trpc', trpcContext); - return startSpanManual( - { - name: `trpc/${path}`, - op: 'rpc.server', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.rpc.trpc', + return withScope(scope => { + scope.setContext('trpc', trpcContext); + return startSpanManual( + { + name: `trpc/${path}`, + op: 'rpc.server', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.rpc.trpc', + }, }, - }, - async span => { - try { - const nextResult = await next(); - captureIfError(nextResult); - span.end(); - return nextResult; - } catch (e) { - captureException(e, trpcCaptureContext); - span.end(); - throw e; - } - }, - ) as SentryTrpcMiddleware; + async span => { + try { + const nextResult = await next(); + captureIfError(nextResult); + span.end(); + return nextResult; + } catch (e) { + captureException(e, trpcCaptureContext); + span.end(); + throw e; + } + }, + ) as SentryTrpcMiddleware; + }); }; } From b45b885d9f73b083d6b3fa740f4e2ab41788d650 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 11 Nov 2024 10:47:35 +0100 Subject: [PATCH 12/24] ref: Add external contributor to CHANGELOG.md (#14220) This PR adds the external contributor to the CHANGELOG.md file, so that they are credited for their contribution. See #14218 Co-authored-by: lforst <8118419+lforst@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a416a63eed4..4a9fa5699349 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -Work in this release was contributed by @gilisho. Thank you for your contribution! +Work in this release was contributed by @gilisho and @phuctm97. Thank you for your contributions! ## 8.37.1 From 127083eb4747e7d0bbe138c3f79e710c0315ad59 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Mon, 11 Nov 2024 14:38:02 +0000 Subject: [PATCH 13/24] fix(react): Support wildcard routes on React Router 6 (#14205) Based on @grahamhency's work at https://github.com/getsentry/sentry-javascript/pull/14076. Sets correct transaction names when wildcards are used in React Router 6 routes. This does not fix the usage of the descendant routes (https://github.com/getsentry/sentry-javascript/issues/5997), which we should try to address separately. --- packages/react/src/reactrouterv6.tsx | 38 ++++- packages/react/test/reactrouterv6.test.tsx | 178 +++++++++++++++++++++ 2 files changed, 213 insertions(+), 3 deletions(-) diff --git a/packages/react/src/reactrouterv6.tsx b/packages/react/src/reactrouterv6.tsx index 1ec1ae4b7d35..2576ba664f40 100644 --- a/packages/react/src/reactrouterv6.tsx +++ b/packages/react/src/reactrouterv6.tsx @@ -1,3 +1,4 @@ +/* eslint-disable max-lines */ // Inspired from Donnie McNeal's solution: // https://gist.github.com/wontondon/e8c4bdf2888875e4c755712e99279536 @@ -141,6 +142,29 @@ function stripBasenameFromPathname(pathname: string, basename: string): string { return pathname.slice(startIndex) || '/'; } +function sendIndexPath(pathBuilder: string, pathname: string, basename: string): [string, TransactionSource] { + const reconstructedPath = pathBuilder || _stripBasename ? stripBasenameFromPathname(pathname, basename) : pathname; + + const formattedPath = + // If the path ends with a slash, remove it + reconstructedPath[reconstructedPath.length - 1] === '/' + ? reconstructedPath.slice(0, -1) + : // If the path ends with a wildcard, remove it + reconstructedPath.slice(-2) === '/*' + ? reconstructedPath.slice(0, -1) + : reconstructedPath; + + return [formattedPath, 'route']; +} + +function pathEndsWithWildcard(path: string, branch: RouteMatch): boolean { + return (path.slice(-2) === '/*' && branch.route.children && branch.route.children.length > 0) || false; +} + +function pathIsWildcardAndHasChildren(path: string, branch: RouteMatch): boolean { + return (path === '*' && branch.route.children && branch.route.children.length > 0) || false; +} + function getNormalizedName( routes: RouteObject[], location: Location, @@ -158,14 +182,16 @@ function getNormalizedName( if (route) { // Early return if index route if (route.index) { - return [_stripBasename ? stripBasenameFromPathname(branch.pathname, basename) : branch.pathname, 'route']; + return sendIndexPath(pathBuilder, branch.pathname, basename); } - const path = route.path; - if (path) { + + // If path is not a wildcard and has no child routes, append the path + if (path && !pathIsWildcardAndHasChildren(path, branch)) { const newPath = path[0] === '/' || pathBuilder[pathBuilder.length - 1] === '/' ? path : `/${path}`; pathBuilder += newPath; + // If the path matches the current location, return the path if (basename + branch.pathname === location.pathname) { if ( // If the route defined on the element is something like @@ -177,6 +203,12 @@ function getNormalizedName( ) { return [(_stripBasename ? '' : basename) + newPath, 'route']; } + + // if the last character of the pathbuilder is a wildcard and there are children, remove the wildcard + if (pathEndsWithWildcard(pathBuilder, branch)) { + pathBuilder = pathBuilder.slice(0, -1); + } + return [(_stripBasename ? '' : basename) + pathBuilder, 'route']; } } diff --git a/packages/react/test/reactrouterv6.test.tsx b/packages/react/test/reactrouterv6.test.tsx index fbb135a5449c..de65159c56e4 100644 --- a/packages/react/test/reactrouterv6.test.tsx +++ b/packages/react/test/reactrouterv6.test.tsx @@ -391,6 +391,106 @@ describe('reactRouterV6BrowserTracingIntegration', () => { }); }); + it('works with wildcard routes', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + reactRouterV6BrowserTracingIntegration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const SentryRoutes = withSentryReactRouterV6Routing(Routes); + + render( + + + }> + } /> + Account Page} /> + + }> + Project Page}> + Project Page Root} /> + Editor}> + View Canvas} /> + Space Canvas} /> + + + + + No Match Page} /> + + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/projects/:projectId/views/:viewId', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + + it('works with nested wildcard routes', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + reactRouterV6BrowserTracingIntegration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const SentryRoutes = withSentryReactRouterV6Routing(Routes); + + render( + + + }> + } /> + Account Page} /> + + }> + Project Page}> + Project Page Root} /> + Editor}> + }> + View Canvas} /> + Space Canvas} /> + + + + + + No Match Page} /> + + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/projects/:projectId/views/:viewId', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + it("updates the scope's `transactionName` on a navigation", () => { const client = createMockBrowserClient(); setCurrentClient(client); @@ -849,6 +949,84 @@ describe('reactRouterV6BrowserTracingIntegration', () => { }); }); + it('works with wildcard routes', () => { + const client = createMockBrowserClient(); + setCurrentClient(client); + + client.addIntegration( + reactRouterV6BrowserTracingIntegration({ + useEffect: React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + }), + ); + const wrappedUseRoutes = wrapUseRoutes(useRoutes); + + const Routes = () => + wrappedUseRoutes([ + { + index: true, + element: , + }, + { + path: '*', + element: <>, + children: [ + { + path: 'profile', + element: <>, + }, + { + path: 'param-page', + element: , + children: [ + { + path: '*', + element: , + children: [ + { + path: ':id', + element: <>, + children: [ + { + element: <>, + path: 'details', + children: [ + { + element: <>, + path: ':superId', + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ]); + + render( + + + , + ); + + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenCalledTimes(1); + expect(mockStartBrowserTracingNavigationSpan).toHaveBeenLastCalledWith(expect.any(BrowserClient), { + name: '/param-page/:id/details/:superId', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react.reactrouter_v6', + }, + }); + }); + it('does not add double slashes to URLS', () => { const client = createMockBrowserClient(); setCurrentClient(client); From d90d5b8b07997d5a5a269250fb3b93dcd8cad3de Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 11 Nov 2024 09:42:07 -0500 Subject: [PATCH 14/24] docs: Improve docstrings for node otel integrations (#14217) Related to https://github.com/getsentry/sentry-javascript/issues/13317 Improve the docstrings and link to documentation pages as appropriate (as they contain information about opentelemetry and supported versions). --- .../node/src/integrations/tracing/amqplib.ts | 14 +++++++ .../node/src/integrations/tracing/connect.ts | 35 +++++++++++++++++ .../src/integrations/tracing/dataloader.ts | 13 ++++++- .../node/src/integrations/tracing/express.ts | 38 +++++++++++++++++-- .../node/src/integrations/tracing/fastify.ts | 33 ++++++++++++++-- .../src/integrations/tracing/genericPool.ts | 13 ++++++- .../node/src/integrations/tracing/graphql.ts | 14 ++++++- .../src/integrations/tracing/hapi/index.ts | 32 +++++++++++++++- .../node/src/integrations/tracing/kafka.ts | 12 +++++- packages/node/src/integrations/tracing/koa.ts | 38 +++++++++++++++++++ .../src/integrations/tracing/lrumemoizer.ts | 12 +++++- .../node/src/integrations/tracing/mongo.ts | 13 ++++++- .../node/src/integrations/tracing/mongoose.ts | 13 ++++++- .../node/src/integrations/tracing/mysql.ts | 13 ++++++- .../node/src/integrations/tracing/mysql2.ts | 13 ++++++- .../node/src/integrations/tracing/postgres.ts | 13 ++++++- .../node/src/integrations/tracing/prisma.ts | 30 ++++++++++++--- .../node/src/integrations/tracing/redis.ts | 14 ++++++- .../node/src/integrations/tracing/tedious.ts | 13 ++++++- 19 files changed, 337 insertions(+), 39 deletions(-) diff --git a/packages/node/src/integrations/tracing/amqplib.ts b/packages/node/src/integrations/tracing/amqplib.ts index 4b44a145ae1f..d56dd365924c 100644 --- a/packages/node/src/integrations/tracing/amqplib.ts +++ b/packages/node/src/integrations/tracing/amqplib.ts @@ -27,4 +27,18 @@ const _amqplibIntegration = (() => { }; }) satisfies IntegrationFn; +/** + * Adds Sentry tracing instrumentation for the [amqplib](https://www.npmjs.com/package/amqplib) library. + * + * For more information, see the [`amqplibIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/amqplib/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.amqplibIntegration()], + * }); + * ``` + */ export const amqplibIntegration = defineIntegration(_amqplibIntegration); diff --git a/packages/node/src/integrations/tracing/connect.ts b/packages/node/src/integrations/tracing/connect.ts index 5ea6011c5257..b2938d5ee6f2 100644 --- a/packages/node/src/integrations/tracing/connect.ts +++ b/packages/node/src/integrations/tracing/connect.ts @@ -29,6 +29,22 @@ const _connectIntegration = (() => { }; }) satisfies IntegrationFn; +/** + * Adds Sentry tracing instrumentation for [Connect](https://github.com/senchalabs/connect/). + * + * If you also want to capture errors, you need to call `setupConnectErrorHandler(app)` after you initialize your connect app. + * + * For more information, see the [connect documentation](https://docs.sentry.io/platforms/javascript/guides/connect/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.connectIntegration()], + * }) + * ``` + */ export const connectIntegration = defineIntegration(_connectIntegration); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -37,6 +53,25 @@ function connectErrorMiddleware(err: any, req: any, res: any, next: any): void { next(err); } +/** + * Add a Connect middleware to capture errors to Sentry. + * + * @param app The Connect app to attach the error handler to + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * const connect = require("connect"); + * + * const app = connect(); + * + * Sentry.setupConnectErrorHandler(app); + * + * // Add you connect routes here + * + * app.listen(3000); + * ``` + */ export const setupConnectErrorHandler = (app: ConnectApp): void => { app.use(connectErrorMiddleware); diff --git a/packages/node/src/integrations/tracing/dataloader.ts b/packages/node/src/integrations/tracing/dataloader.ts index d4567ea0dfbe..87a479cf0589 100644 --- a/packages/node/src/integrations/tracing/dataloader.ts +++ b/packages/node/src/integrations/tracing/dataloader.ts @@ -50,8 +50,17 @@ const _dataloaderIntegration = (() => { }) satisfies IntegrationFn; /** - * Dataloader integration + * Adds Sentry tracing instrumentation for the [dataloader](https://www.npmjs.com/package/dataloader) library. * - * Capture tracing data for Dataloader. + * For more information, see the [`dataloaderIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/dataloader/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.dataloaderIntegration()], + * }); + * ``` */ export const dataloaderIntegration = defineIntegration(_dataloaderIntegration); diff --git a/packages/node/src/integrations/tracing/express.ts b/packages/node/src/integrations/tracing/express.ts index b8c50e0eb621..0b4b6aa35c3e 100644 --- a/packages/node/src/integrations/tracing/express.ts +++ b/packages/node/src/integrations/tracing/express.ts @@ -60,10 +60,20 @@ const _expressIntegration = (() => { }) satisfies IntegrationFn; /** - * Express integration + * Adds Sentry tracing instrumentation for [Express](https://expressjs.com/). * - * Capture tracing data for express. - * In order to capture exceptions, you have to call `setupExpressErrorHandler(app)` before any other middleware and after all controllers. + * If you also want to capture errors, you need to call `setupExpressErrorHandler(app)` after you set up your Express server. + * + * For more information, see the [express documentation](https://docs.sentry.io/platforms/javascript/guides/express/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.expressIntegration()], + * }) + * ``` */ export const expressIntegration = defineIntegration(_expressIntegration); @@ -134,8 +144,28 @@ export function expressErrorHandler(options?: ExpressHandlerOptions): ExpressMid } /** - * Setup an error handler for Express. + * Add an Express error handler to capture errors to Sentry. + * * The error handler must be before any other middleware and after all controllers. + * + * @param app The Express instances + * @param options {ExpressHandlerOptions} Configuration options for the handler + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * const express = require("express"); + * + * const app = express(); + * + * // Add your routes, etc. + * + * // Add this after all routes, + * // but before any and other error-handling middlewares are defined + * Sentry.setupExpressErrorHandler(app); + * + * app.listen(3000); + * ``` */ export function setupExpressErrorHandler( app: { use: (middleware: ExpressMiddleware) => unknown }, diff --git a/packages/node/src/integrations/tracing/fastify.ts b/packages/node/src/integrations/tracing/fastify.ts index 27657d94d3d3..5a75e204b5f5 100644 --- a/packages/node/src/integrations/tracing/fastify.ts +++ b/packages/node/src/integrations/tracing/fastify.ts @@ -55,14 +55,41 @@ const _fastifyIntegration = (() => { }) satisfies IntegrationFn; /** - * Express integration + * Adds Sentry tracing instrumentation for [Fastify](https://fastify.dev/). * - * Capture tracing data for fastify. + * If you also want to capture errors, you need to call `setupFastifyErrorHandler(app)` after you set up your Fastify server. + * + * For more information, see the [fastify documentation](https://docs.sentry.io/platforms/javascript/guides/fastify/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.fastifyIntegration()], + * }) + * ``` */ export const fastifyIntegration = defineIntegration(_fastifyIntegration); /** - * Setup an error handler for Fastify. + * Add an Fastify error handler to capture errors to Sentry. + * + * @param fastify The Fastify instance to which to add the error handler + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * const Fastify = require("fastify"); + * + * const app = Fastify(); + * + * Sentry.setupFastifyErrorHandler(app); + * + * // Add your routes, etc. + * + * app.listen({ port: 3000 }); + * ``` */ export function setupFastifyErrorHandler(fastify: Fastify): void { const plugin = Object.assign( diff --git a/packages/node/src/integrations/tracing/genericPool.ts b/packages/node/src/integrations/tracing/genericPool.ts index 8bc84554071c..ab3e5c25a207 100644 --- a/packages/node/src/integrations/tracing/genericPool.ts +++ b/packages/node/src/integrations/tracing/genericPool.ts @@ -33,8 +33,17 @@ const _genericPoolIntegration = (() => { }) satisfies IntegrationFn; /** - * GenericPool integration + * Adds Sentry tracing instrumentation for the [generic-pool](https://www.npmjs.com/package/generic-pool) library. * - * Capture tracing data for GenericPool. + * For more information, see the [`genericPoolIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/genericpool/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.genericPoolIntegration()], + * }); + * ``` */ export const genericPoolIntegration = defineIntegration(_genericPoolIntegration); diff --git a/packages/node/src/integrations/tracing/graphql.ts b/packages/node/src/integrations/tracing/graphql.ts index f4d817145cf2..c2f1402a0229 100644 --- a/packages/node/src/integrations/tracing/graphql.ts +++ b/packages/node/src/integrations/tracing/graphql.ts @@ -93,9 +93,19 @@ const _graphqlIntegration = ((options: GraphqlOptions = {}) => { }) satisfies IntegrationFn; /** - * GraphQL integration + * Adds Sentry tracing instrumentation for the [graphql](https://www.npmjs.com/package/graphql) library. * - * Capture tracing data for GraphQL. + * For more information, see the [`graphqlIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/graphql/). + * + * @param {GraphqlOptions} options Configuration options for the GraphQL integration. + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.graphqlIntegration()], + * }); */ export const graphqlIntegration = defineIntegration(_graphqlIntegration); diff --git a/packages/node/src/integrations/tracing/hapi/index.ts b/packages/node/src/integrations/tracing/hapi/index.ts index be452636bef8..9d1015704d60 100644 --- a/packages/node/src/integrations/tracing/hapi/index.ts +++ b/packages/node/src/integrations/tracing/hapi/index.ts @@ -31,10 +31,20 @@ const _hapiIntegration = (() => { }) satisfies IntegrationFn; /** - * Hapi integration + * Adds Sentry tracing instrumentation for [Hapi](https://hapi.dev/). * - * Capture tracing data for Hapi. * If you also want to capture errors, you need to call `setupHapiErrorHandler(server)` after you set up your server. + * + * For more information, see the [hapi documentation](https://docs.sentry.io/platforms/javascript/guides/hapi/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.hapiIntegration()], + * }) + * ``` */ export const hapiIntegration = defineIntegration(_hapiIntegration); @@ -81,6 +91,24 @@ export const hapiErrorPlugin = { /** * Add a Hapi plugin to capture errors to Sentry. + * + * @param server The Hapi server to attach the error handler to + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * const Hapi = require('@hapi/hapi'); + * + * const init = async () => { + * const server = Hapi.server(); + * + * // all your routes here + * + * await Sentry.setupHapiErrorHandler(server); + * + * await server.start(); + * }; + * ``` */ export async function setupHapiErrorHandler(server: Server): Promise { await server.register(hapiErrorPlugin); diff --git a/packages/node/src/integrations/tracing/kafka.ts b/packages/node/src/integrations/tracing/kafka.ts index 7bdab00459e1..68a9ccf8bab4 100644 --- a/packages/node/src/integrations/tracing/kafka.ts +++ b/packages/node/src/integrations/tracing/kafka.ts @@ -30,8 +30,16 @@ const _kafkaIntegration = (() => { }) satisfies IntegrationFn; /** - * KafkaJs integration + * Adds Sentry tracing instrumentation for the [kafkajs](https://www.npmjs.com/package/kafkajs) library. * - * Capture tracing data for KafkaJs. + * For more information, see the [`kafkaIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/kafka/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.kafkaIntegration()], + * }); */ export const kafkaIntegration = defineIntegration(_kafkaIntegration); diff --git a/packages/node/src/integrations/tracing/koa.ts b/packages/node/src/integrations/tracing/koa.ts index 15ddc4658fa9..a244c6cb56d7 100644 --- a/packages/node/src/integrations/tracing/koa.ts +++ b/packages/node/src/integrations/tracing/koa.ts @@ -48,8 +48,46 @@ const _koaIntegration = (() => { }; }) satisfies IntegrationFn; +/** + * Adds Sentry tracing instrumentation for [Koa](https://koajs.com/). + * + * If you also want to capture errors, you need to call `setupKoaErrorHandler(app)` after you set up your Koa server. + * + * For more information, see the [koa documentation](https://docs.sentry.io/platforms/javascript/guides/koa/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.koaIntegration()], + * }) + * ``` + */ export const koaIntegration = defineIntegration(_koaIntegration); +/** + * Add an Koa error handler to capture errors to Sentry. + * + * The error handler must be before any other middleware and after all controllers. + * + * @param app The Express instances + * @param options {ExpressHandlerOptions} Configuration options for the handler + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * const Koa = require("koa"); + * + * const app = new Koa(); + * + * Sentry.setupKoaErrorHandler(app); + * + * // Add your routes, etc. + * + * app.listen(3000); + * ``` + */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export const setupKoaErrorHandler = (app: { use: (arg0: (ctx: any, next: any) => Promise) => void }): void => { app.use(async (ctx, next) => { diff --git a/packages/node/src/integrations/tracing/lrumemoizer.ts b/packages/node/src/integrations/tracing/lrumemoizer.ts index d94234c3e57d..6c8a1962338c 100644 --- a/packages/node/src/integrations/tracing/lrumemoizer.ts +++ b/packages/node/src/integrations/tracing/lrumemoizer.ts @@ -18,8 +18,16 @@ const _lruMemoizerIntegration = (() => { }) satisfies IntegrationFn; /** - * LruMemoizer integration + * Adds Sentry tracing instrumentation for the [lru-memoizer](https://www.npmjs.com/package/lru-memoizer) library. * - * Propagate traces through LruMemoizer. + * For more information, see the [`lruMemoizerIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/lrumemoizer/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.lruMemoizerIntegration()], + * }); */ export const lruMemoizerIntegration = defineIntegration(_lruMemoizerIntegration); diff --git a/packages/node/src/integrations/tracing/mongo.ts b/packages/node/src/integrations/tracing/mongo.ts index 143c7bf99a6d..5e42f5611db8 100644 --- a/packages/node/src/integrations/tracing/mongo.ts +++ b/packages/node/src/integrations/tracing/mongo.ts @@ -27,8 +27,17 @@ const _mongoIntegration = (() => { }) satisfies IntegrationFn; /** - * MongoDB integration + * Adds Sentry tracing instrumentation for the [mongodb](https://www.npmjs.com/package/mongodb) library. * - * Capture tracing data for MongoDB. + * For more information, see the [`mongoIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/mongo/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.mongoIntegration()], + * }); + * ``` */ export const mongoIntegration = defineIntegration(_mongoIntegration); diff --git a/packages/node/src/integrations/tracing/mongoose.ts b/packages/node/src/integrations/tracing/mongoose.ts index 4a4566fa98da..58234c1ec87f 100644 --- a/packages/node/src/integrations/tracing/mongoose.ts +++ b/packages/node/src/integrations/tracing/mongoose.ts @@ -27,8 +27,17 @@ const _mongooseIntegration = (() => { }) satisfies IntegrationFn; /** - * Mongoose integration + * Adds Sentry tracing instrumentation for the [mongoose](https://www.npmjs.com/package/mongoose) library. * - * Capture tracing data for Mongoose. + * For more information, see the [`mongooseIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/mongoose/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.mongooseIntegration()], + * }); + * ``` */ export const mongooseIntegration = defineIntegration(_mongooseIntegration); diff --git a/packages/node/src/integrations/tracing/mysql.ts b/packages/node/src/integrations/tracing/mysql.ts index 67b46ae9bdcf..d6509de0f765 100644 --- a/packages/node/src/integrations/tracing/mysql.ts +++ b/packages/node/src/integrations/tracing/mysql.ts @@ -17,8 +17,17 @@ const _mysqlIntegration = (() => { }) satisfies IntegrationFn; /** - * MySQL integration + * Adds Sentry tracing instrumentation for the [mysql](https://www.npmjs.com/package/mysql) library. * - * Capture tracing data for mysql. + * For more information, see the [`mysqlIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/mysql/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.mysqlIntegration()], + * }); + * ``` */ export const mysqlIntegration = defineIntegration(_mysqlIntegration); diff --git a/packages/node/src/integrations/tracing/mysql2.ts b/packages/node/src/integrations/tracing/mysql2.ts index b3c36435979c..c44fa2433528 100644 --- a/packages/node/src/integrations/tracing/mysql2.ts +++ b/packages/node/src/integrations/tracing/mysql2.ts @@ -27,8 +27,17 @@ const _mysql2Integration = (() => { }) satisfies IntegrationFn; /** - * MySQL2 integration + * Adds Sentry tracing instrumentation for the [mysql2](https://www.npmjs.com/package/mysql2) library. * - * Capture tracing data for mysql2 + * For more information, see the [`mysql2Integration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/mysql2/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.mysqlIntegration()], + * }); + * ``` */ export const mysql2Integration = defineIntegration(_mysql2Integration); diff --git a/packages/node/src/integrations/tracing/postgres.ts b/packages/node/src/integrations/tracing/postgres.ts index 05b56d9152ff..6df045156d41 100644 --- a/packages/node/src/integrations/tracing/postgres.ts +++ b/packages/node/src/integrations/tracing/postgres.ts @@ -28,8 +28,17 @@ const _postgresIntegration = (() => { }) satisfies IntegrationFn; /** - * Postgres integration + * Adds Sentry tracing instrumentation for the [pg](https://www.npmjs.com/package/pg) library. * - * Capture tracing data for pg. + * For more information, see the [`postgresIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/postgres/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.postgresIntegration()], + * }); + * ``` */ export const postgresIntegration = defineIntegration(_postgresIntegration); diff --git a/packages/node/src/integrations/tracing/prisma.ts b/packages/node/src/integrations/tracing/prisma.ts index c9e7acafb09d..56768bf1ba71 100644 --- a/packages/node/src/integrations/tracing/prisma.ts +++ b/packages/node/src/integrations/tracing/prisma.ts @@ -38,12 +38,30 @@ const _prismaIntegration = (() => { }) satisfies IntegrationFn; /** - * Prisma integration + * Adds Sentry tracing instrumentation for the [prisma](https://www.npmjs.com/package/prisma) library. * - * Capture tracing data for prisma. - * Note: This requires to set: - * previewFeatures = ["tracing"] - * For the prisma client. - * See https://www.prisma.io/docs/concepts/components/prisma-client/opentelemetry-tracing for more details. + * For more information, see the [`prismaIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/prisma/). + * + * @example + * + * Make sure `previewFeatures = ["tracing"]` is set in the prisma client generator block. See the + * [prisma docs](https://www.prisma.io/docs/concepts/components/prisma-client/opentelemetry-tracing) for more details. + * + * ```prisma + * generator client { + * provider = "prisma-client-js" + * previewFeatures = ["tracing"] + * } + * ``` + * + * Then you can use the integration like this: + * + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.prismaIntegration()], + * }); + * ``` */ export const prismaIntegration = defineIntegration(_prismaIntegration); diff --git a/packages/node/src/integrations/tracing/redis.ts b/packages/node/src/integrations/tracing/redis.ts index 4204e4a2abe5..cf30d8f953f3 100644 --- a/packages/node/src/integrations/tracing/redis.ts +++ b/packages/node/src/integrations/tracing/redis.ts @@ -110,8 +110,18 @@ const _redisIntegration = ((options: RedisOptions = {}) => { }) satisfies IntegrationFn; /** - * Redis integration for "ioredis" + * Adds Sentry tracing instrumentation for the [redis](https://www.npmjs.com/package/redis) and + * [ioredis](https://www.npmjs.com/package/ioredis) libraries. * - * Capture tracing data for redis and ioredis. + * For more information, see the [`redisIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/redis/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.redisIntegration()], + * }); + * ``` */ export const redisIntegration = defineIntegration(_redisIntegration); diff --git a/packages/node/src/integrations/tracing/tedious.ts b/packages/node/src/integrations/tracing/tedious.ts index 7cde3f69311d..6ad1cf56f28e 100644 --- a/packages/node/src/integrations/tracing/tedious.ts +++ b/packages/node/src/integrations/tracing/tedious.ts @@ -41,8 +41,17 @@ const _tediousIntegration = (() => { }) satisfies IntegrationFn; /** - * Tedious integration + * Adds Sentry tracing instrumentation for the [tedious](https://www.npmjs.com/package/tedious) library. * - * Capture tracing data for tedious. + * For more information, see the [`tediousIntegration` documentation](https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/tedious/). + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * + * Sentry.init({ + * integrations: [Sentry.tediousIntegration()], + * }); + * ``` */ export const tediousIntegration = defineIntegration(_tediousIntegration); From 23c4ad16dc7cb2f220d67d70be9cf1f021b0f0ce Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 11 Nov 2024 16:06:51 +0100 Subject: [PATCH 15/24] ref: Add external contributor to CHANGELOG.md (#14206) This PR adds the external contributor to the CHANGELOG.md file, so that they are credited for their contribution. See #13486 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9fa5699349..002b3549fa20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -Work in this release was contributed by @gilisho and @phuctm97. Thank you for your contributions! +Work in this release was contributed by @Zen-cronic, @gilisho and @phuctm97. Thank you for your contributions! ## 8.37.1 From 1b925846aa2dd5040a4dcffbd9a08c97a0ec7b71 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 11 Nov 2024 10:49:23 -0500 Subject: [PATCH 16/24] build: Update external contributors action to use chore instead of ref (#14229) We aren't changing any user code, so let's avoid using `ref` in the commit. --- .github/workflows/external-contributors.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/external-contributors.yml b/.github/workflows/external-contributors.yml index aac1805c1bb5..e9b1e05a2c92 100644 --- a/.github/workflows/external-contributors.yml +++ b/.github/workflows/external-contributors.yml @@ -41,8 +41,8 @@ jobs: # This token is scoped to Daniel Griesser # If we used the default GITHUB_TOKEN, the resulting PR would not trigger CI :( token: ${{ secrets.REPO_SCOPED_TOKEN }} - commit-message: "ref: Add external contributor to CHANGELOG.md" - title: "ref: Add external contributor to CHANGELOG.md" + commit-message: "chore: Add external contributor to CHANGELOG.md" + title: "chore: Add external contributor to CHANGELOG.md" branch: 'external-contributor/patch-${{ github.event.pull_request.user.login }}' base: 'develop' delete-branch: true From 8532e259995ace94907b8387dd4327b916fad806 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 11 Nov 2024 10:49:34 -0500 Subject: [PATCH 17/24] chore: Add external contributor to changelog (#14228) Add @grahamhency to changelog for work in https://github.com/getsentry/sentry-javascript/pull/14205 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 002b3549fa20..cc925a41e558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -Work in this release was contributed by @Zen-cronic, @gilisho and @phuctm97. Thank you for your contributions! +Work in this release was contributed by @grahamhency, @Zen-cronic, @gilisho and @phuctm97. Thank you for your contributions! ## 8.37.1 From fac72097c7bc3de9ce738e080b746c35fea23099 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 11 Nov 2024 18:09:10 +0100 Subject: [PATCH 18/24] fix(node): Include `debug_meta` with ANR events (#14203) Sends a map of filename -> debug ids to the ANR worker thread and sends an updated list every time more modules are loaded. These are then used and included with events so they can be symbolicated --- .../suites/anr/basic.js | 2 + .../suites/anr/basic.mjs | 2 + .../node-integration-tests/suites/anr/test.ts | 21 +++++++++- packages/node/src/integrations/anr/index.ts | 18 ++++++++- packages/node/src/integrations/anr/worker.ts | 40 ++++++++++++++++++- 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/anr/basic.js b/dev-packages/node-integration-tests/suites/anr/basic.js index b1dddf958d46..e2adf0e8c60f 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic.js +++ b/dev-packages/node-integration-tests/suites/anr/basic.js @@ -3,6 +3,8 @@ const assert = require('assert'); const Sentry = require('@sentry/node'); +global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; + setTimeout(() => { process.exit(); }, 10000); diff --git a/dev-packages/node-integration-tests/suites/anr/basic.mjs b/dev-packages/node-integration-tests/suites/anr/basic.mjs index c3e74222f587..18777e5ecdbd 100644 --- a/dev-packages/node-integration-tests/suites/anr/basic.mjs +++ b/dev-packages/node-integration-tests/suites/anr/basic.mjs @@ -3,6 +3,8 @@ import * as crypto from 'crypto'; import * as Sentry from '@sentry/node'; +global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; + setTimeout(() => { process.exit(); }, 10000); diff --git a/dev-packages/node-integration-tests/suites/anr/test.ts b/dev-packages/node-integration-tests/suites/anr/test.ts index 78f89d7451c0..0352212a8293 100644 --- a/dev-packages/node-integration-tests/suites/anr/test.ts +++ b/dev-packages/node-integration-tests/suites/anr/test.ts @@ -1,3 +1,4 @@ +import type { Event } from '@sentry/types'; import { conditionalTest } from '../../utils'; import { cleanupChildProcesses, createRunner } from '../../utils/runner'; @@ -64,17 +65,33 @@ const ANR_EVENT_WITH_SCOPE = { ]), }; +const ANR_EVENT_WITH_DEBUG_META: Event = { + ...ANR_EVENT_WITH_SCOPE, + debug_meta: { + images: [ + { + type: 'sourcemap', + debug_id: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa', + code_file: expect.stringContaining('basic.'), + }, + ], + }, +}; + conditionalTest({ min: 16 })('should report ANR when event loop blocked', () => { afterAll(() => { cleanupChildProcesses(); }); test('CJS', done => { - createRunner(__dirname, 'basic.js').withMockSentryServer().expect({ event: ANR_EVENT_WITH_SCOPE }).start(done); + createRunner(__dirname, 'basic.js').withMockSentryServer().expect({ event: ANR_EVENT_WITH_DEBUG_META }).start(done); }); test('ESM', done => { - createRunner(__dirname, 'basic.mjs').withMockSentryServer().expect({ event: ANR_EVENT_WITH_SCOPE }).start(done); + createRunner(__dirname, 'basic.mjs') + .withMockSentryServer() + .expect({ event: ANR_EVENT_WITH_DEBUG_META }) + .start(done); }); test('blocked indefinitely', done => { diff --git a/packages/node/src/integrations/anr/index.ts b/packages/node/src/integrations/anr/index.ts index 92a4078aa766..c5f5b28e0888 100644 --- a/packages/node/src/integrations/anr/index.ts +++ b/packages/node/src/integrations/anr/index.ts @@ -1,7 +1,8 @@ +import * as diagnosticsChannel from 'node:diagnostics_channel'; import { Worker } from 'node:worker_threads'; import { defineIntegration, getCurrentScope, getGlobalScope, getIsolationScope, mergeScopeData } from '@sentry/core'; import type { Contexts, Event, EventHint, Integration, IntegrationFn, ScopeData } from '@sentry/types'; -import { GLOBAL_OBJ, logger } from '@sentry/utils'; +import { GLOBAL_OBJ, getFilenameToDebugIdMap, logger } from '@sentry/utils'; import { NODE_VERSION } from '../../nodeVersion'; import type { NodeClient } from '../../sdk/client'; import type { AnrIntegrationOptions, WorkerStartData } from './common'; @@ -100,6 +101,13 @@ type AnrReturn = (options?: Partial) => Integration & Anr export const anrIntegration = defineIntegration(_anrIntegration) as AnrReturn; +function onModuleLoad(callback: () => void): void { + // eslint-disable-next-line deprecation/deprecation + diagnosticsChannel.channel('module.require.end').subscribe(() => callback()); + // eslint-disable-next-line deprecation/deprecation + diagnosticsChannel.channel('module.import.asyncEnd').subscribe(() => callback()); +} + /** * Starts the ANR worker thread * @@ -153,6 +161,12 @@ async function _startWorker( } } + let debugImages: Record = getFilenameToDebugIdMap(initOptions.stackParser); + + onModuleLoad(() => { + debugImages = getFilenameToDebugIdMap(initOptions.stackParser); + }); + const worker = new Worker(new URL(`data:application/javascript;base64,${base64WorkerScript}`), { workerData: options, // We don't want any Node args to be passed to the worker @@ -171,7 +185,7 @@ async function _startWorker( // serialized without making it a SerializedSession const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined; // message the worker to tell it the main event loop is still running - worker.postMessage({ session }); + worker.postMessage({ session, debugImages }); } catch (_) { // } diff --git a/packages/node/src/integrations/anr/worker.ts b/packages/node/src/integrations/anr/worker.ts index 67532435a39e..8e20fbeeb39a 100644 --- a/packages/node/src/integrations/anr/worker.ts +++ b/packages/node/src/integrations/anr/worker.ts @@ -8,7 +8,7 @@ import { makeSession, updateSession, } from '@sentry/core'; -import type { Event, ScopeData, Session, StackFrame } from '@sentry/types'; +import type { DebugImage, Event, ScopeData, Session, StackFrame } from '@sentry/types'; import { callFrameToStackFrame, normalizeUrlToBase, @@ -26,6 +26,7 @@ type VoidFunction = () => void; const options: WorkerStartData = workerData; let session: Session | undefined; let hasSentAnrEvent = false; +let mainDebugImages: Record = {}; function log(msg: string): void { if (options.debug) { @@ -87,6 +88,35 @@ function prepareStackFrames(stackFrames: StackFrame[] | undefined): StackFrame[] return strippedFrames; } +function applyDebugMeta(event: Event): void { + if (Object.keys(mainDebugImages).length === 0) { + return; + } + + const filenameToDebugId = new Map(); + + for (const exception of event.exception?.values || []) { + for (const frame of exception.stacktrace?.frames || []) { + const filename = frame.abs_path || frame.filename; + if (filename && mainDebugImages[filename]) { + filenameToDebugId.set(filename, mainDebugImages[filename] as string); + } + } + } + + if (filenameToDebugId.size > 0) { + const images: DebugImage[] = []; + for (const [filename, debugId] of filenameToDebugId.entries()) { + images.push({ + type: 'sourcemap', + code_file: filename, + debug_id: debugId, + }); + } + event.debug_meta = { images }; + } +} + function applyScopeToEvent(event: Event, scope: ScopeData): void { applyScopeDataToEvent(event, scope); @@ -140,6 +170,8 @@ async function sendAnrEvent(frames?: StackFrame[], scope?: ScopeData): Promise { +parentPort?.on('message', (msg: { session: Session | undefined; debugImages?: Record }) => { if (msg.session) { session = makeSession(msg.session); } + if (msg.debugImages) { + mainDebugImages = msg.debugImages; + } + poll(); }); From 48c3594f2494bc967f5092b517b048483d318979 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 17:14:47 +0000 Subject: [PATCH 19/24] feat(deps): Bump @opentelemetry/instrumentation-amqplib from 0.42.0 to 0.43.0 (#14230) --- packages/node/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/node/package.json b/packages/node/package.json index a5b0d2abff21..8de05630c1b5 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -69,7 +69,7 @@ "@opentelemetry/context-async-hooks": "^1.25.1", "@opentelemetry/core": "^1.25.1", "@opentelemetry/instrumentation": "^0.54.0", - "@opentelemetry/instrumentation-amqplib": "^0.42.0", + "@opentelemetry/instrumentation-amqplib": "^0.43.0", "@opentelemetry/instrumentation-connect": "0.40.0", "@opentelemetry/instrumentation-dataloader": "0.12.0", "@opentelemetry/instrumentation-express": "0.44.0", diff --git a/yarn.lock b/yarn.lock index e8474dd23fbe..0db34364b9eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7480,13 +7480,13 @@ "@opentelemetry/context-base" "^0.12.0" semver "^7.1.3" -"@opentelemetry/instrumentation-amqplib@^0.42.0": - version "0.42.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.42.0.tgz#b3cab5a7207736a30d769962eed3af3838f986c4" - integrity sha512-fiuU6OKsqHJiydHWgTRQ7MnIrJ2lEqsdgFtNIH4LbAUJl/5XmrIeoDzDnox+hfkgWK65jsleFuQDtYb5hW1koQ== +"@opentelemetry/instrumentation-amqplib@^0.43.0": + version "0.43.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.43.0.tgz#e18b7d763b69c605a7abf9869e1c278f9bfdc1eb" + integrity sha512-ALjfQC+0dnIEcvNYsbZl/VLh7D2P1HhFF4vicRKHhHFIUV3Shpg4kXgiek5PLhmeKSIPiUB25IYH5RIneclL4A== dependencies: "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/instrumentation" "^0.54.0" "@opentelemetry/semantic-conventions" "^1.27.0" "@opentelemetry/instrumentation-aws-lambda@0.44.0": From 68781c5e7db29e30d5de6fb0ae1aca3dc5dfec36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 17:14:59 +0000 Subject: [PATCH 20/24] feat(deps): Bump @sentry/cli from 2.37.0 to 2.38.2 (#14232) --- packages/remix/package.json | 2 +- yarn.lock | 92 ++++++++++++++++++------------------- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/packages/remix/package.json b/packages/remix/package.json index 61cb4be25825..66ffa6bb9507 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -53,7 +53,7 @@ }, "dependencies": { "@remix-run/router": "1.x", - "@sentry/cli": "^2.37.0", + "@sentry/cli": "^2.38.2", "@sentry/core": "8.37.1", "@sentry/node": "8.37.1", "@sentry/opentelemetry": "8.37.1", diff --git a/yarn.lock b/yarn.lock index 0db34364b9eb..f5a01a5a820c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8523,45 +8523,45 @@ magic-string "0.30.8" unplugin "1.0.1" -"@sentry/cli-darwin@2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.37.0.tgz#9c890c68abf30ceaad27826212a0963b125b8bbf" - integrity sha512-CsusyMvO0eCPSN7H+sKHXS1pf637PWbS4rZak/7giz/z31/6qiXmeMlcL3f9lLZKtFPJmXVFO9uprn1wbBVF8A== - -"@sentry/cli-linux-arm64@2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.37.0.tgz#2070155bade6d72d6b706807c6f365c65f9b82ea" - integrity sha512-2vzUWHLZ3Ct5gpcIlfd/2Qsha+y9M8LXvbZE26VxzYrIkRoLAWcnClBv8m4XsHLMURYvz3J9QSZHMZHSO7kAzw== - -"@sentry/cli-linux-arm@2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.37.0.tgz#a08c2133e8e2566074fd6fe4f68e9ffd0c85664a" - integrity sha512-Dz0qH4Yt+gGUgoVsqVt72oDj4VQynRF1QB1/Sr8g76Vbi+WxWZmUh0iFwivYVwWxdQGu/OQrE0tx946HToCRyA== - -"@sentry/cli-linux-i686@2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.37.0.tgz#53fff0e7f232b656b0ee3413b66006ee724a4abf" - integrity sha512-MHRLGs4t/CQE1pG+mZBQixyWL6xDZfNalCjO8GMcTTbZFm44S3XRHfYJZNVCgdtnUP7b6OHGcu1v3SWE10LcwQ== - -"@sentry/cli-linux-x64@2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.37.0.tgz#2fbaf51ef3884bd6561c987f01ac98f544457150" - integrity sha512-k76ClefKZaDNJZU/H3mGeR8uAzAGPzDRG/A7grzKfBeyhP3JW09L7Nz9IQcSjCK+xr399qLhM2HFCaPWQ6dlMw== - -"@sentry/cli-win32-i686@2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.37.0.tgz#fa195664da27ce8c40fdb6db1bf1d125cdf587d9" - integrity sha512-FFyi5RNYQQkEg4GkP2f3BJcgQn0F4fjFDMiWkjCkftNPXQG+HFUEtrGsWr6mnHPdFouwbYg3tEPUWNxAoypvTw== - -"@sentry/cli-win32-x64@2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.37.0.tgz#84fa4d070b8a4a115c46ab38f42d29580143fd26" - integrity sha512-nSMj4OcfQmyL+Tu/jWCJwhKCXFsCZW1MUk6wjjQlRt9SDLfgeapaMlK1ZvT1eZv5ZH6bj3qJfefwj4U8160uOA== - -"@sentry/cli@^2.36.1", "@sentry/cli@^2.37.0": - version "2.37.0" - resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.37.0.tgz#dd01e933cf1caed7d7b6abab5a96044fe1c9c7a1" - integrity sha512-fM3V4gZRJR/s8lafc3O07hhOYRnvkySdPkvL/0e0XW0r+xRwqIAgQ5ECbsZO16A5weUiXVSf03ztDL1FcmbJCQ== +"@sentry/cli-darwin@2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.38.2.tgz#2a53028e143d0cfed607588b87e04906ef5317e7" + integrity sha512-21ywIcJCCFrCTyiF1o1PaT7rbelFC2fWmayKYgFElnQ55IzNYkcn8BYhbh/QknE0l1NBRaeWMXwTTdeoqETCCg== + +"@sentry/cli-linux-arm64@2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.38.2.tgz#1b45de7e4f5e1a953b88b0b811d789de1fc708aa" + integrity sha512-4Fp/jjQpNZj4Th+ZckMQvldAuuP0ZcyJ9tJCP1CCOn5poIKPYtY6zcbTP036R7Te14PS4ALOcDNX3VNKfpsifA== + +"@sentry/cli-linux-arm@2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.38.2.tgz#91f73c251f1d4b591fa98af10ee3889c9b93d208" + integrity sha512-+AiKDBQKIdQe4NhBiHSHGl0KR+b//HHTrnfK1SaTrOm9HtM4ELXAkjkRF3bmbpSzSQCS5WzcbIxxCJOeaUaO0A== + +"@sentry/cli-linux-i686@2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.38.2.tgz#26e30a9bc358f910e21d812359294dd4c6103fda" + integrity sha512-6zVJN10dHIn4R1v+fxuzlblzVBhIVwsaN/S7aBED6Vn1HhAyAcNG2tIzeCLGeDfieYjXlE2sCI82sZkQBCbAGw== + +"@sentry/cli-linux-x64@2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.38.2.tgz#18728bbb20e28315c4368baded677786f2dba70a" + integrity sha512-4UiLu9zdVtqPeltELR5MDGKcuqAdQY9xz3emISuA6bm+MXGbt2W1WgX+XY3GElwjZbmH8qpyLUEd34sw6sdcbQ== + +"@sentry/cli-win32-i686@2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.38.2.tgz#dfe268b041c3e3db556290dba745455d0b2c0d72" + integrity sha512-DYfSvd5qLPerLpIxj3Xu2rRe3CIlpGOOfGSNI6xvJ5D8j6hqbOHlCzvfC4oBWYVYGtxnwQLMeDGJ7o7RMYulig== + +"@sentry/cli-win32-x64@2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.38.2.tgz#e7b5744026ff5f7e84971512bee228620ba5857d" + integrity sha512-W5UX58PKY1hNUHo9YJxWNhGvgvv2uOYHI27KchRiUvFYBIqlUUcIdPZDfyzetDfd8qBCxlAsFnkL2VJSNdpA9A== + +"@sentry/cli@^2.36.1", "@sentry/cli@^2.38.2": + version "2.38.2" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.38.2.tgz#e9a7a9bbeaaade4557de91704d50d131760345d3" + integrity sha512-CR0oujpAnhegK2pBAv6ZReMqbFTuNJLDZLvoD1B+syrKZX+R+oxkgT2e1htsBbht+wGxAsluVWsIAydSws1GAA== dependencies: https-proxy-agent "^5.0.0" node-fetch "^2.6.7" @@ -8569,13 +8569,13 @@ proxy-from-env "^1.1.0" which "^2.0.2" optionalDependencies: - "@sentry/cli-darwin" "2.37.0" - "@sentry/cli-linux-arm" "2.37.0" - "@sentry/cli-linux-arm64" "2.37.0" - "@sentry/cli-linux-i686" "2.37.0" - "@sentry/cli-linux-x64" "2.37.0" - "@sentry/cli-win32-i686" "2.37.0" - "@sentry/cli-win32-x64" "2.37.0" + "@sentry/cli-darwin" "2.38.2" + "@sentry/cli-linux-arm" "2.38.2" + "@sentry/cli-linux-arm64" "2.38.2" + "@sentry/cli-linux-i686" "2.38.2" + "@sentry/cli-linux-x64" "2.38.2" + "@sentry/cli-win32-i686" "2.38.2" + "@sentry/cli-win32-x64" "2.38.2" "@sentry/rollup-plugin@2.22.6": version "2.22.6" From 1f56ffa09f4f0deb4a33a8f1a0e7d272ebfb038e Mon Sep 17 00:00:00 2001 From: Kaung Zin Hein <83657429+Zen-cronic@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:26:27 -0500 Subject: [PATCH 21/24] feat(node): Add `knex` integration (#13526) Implement Knex OTEL instrumentation in `packages/node`. This integration is not enabled by default. Signed-off-by: Kaung Zin Hein Co-authored-by: Abhijeet Prasad --- .../node-integration-tests/package.json | 1 + .../suites/tracing/knex/docker-compose.yml | 23 ++++ .../tracing/knex/scenario-withMysql2.js | 53 +++++++ .../tracing/knex/scenario-withPostgres.js | 53 +++++++ .../suites/tracing/knex/test.ts | 129 ++++++++++++++++++ packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/bun/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/package.json | 9 +- packages/node/src/index.ts | 1 + .../node/src/integrations/tracing/knex.ts | 47 +++++++ packages/remix/src/index.server.ts | 1 + packages/solidstart/src/server/index.ts | 1 + packages/sveltekit/src/server/index.ts | 1 + yarn.lock | 69 +++++++++- 16 files changed, 384 insertions(+), 8 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/knex/docker-compose.yml create mode 100644 dev-packages/node-integration-tests/suites/tracing/knex/scenario-withMysql2.js create mode 100644 dev-packages/node-integration-tests/suites/tracing/knex/scenario-withPostgres.js create mode 100644 dev-packages/node-integration-tests/suites/tracing/knex/test.ts create mode 100644 packages/node/src/integrations/tracing/knex.ts diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 14c25b252f3f..e37952cf2381 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -50,6 +50,7 @@ "graphql": "^16.3.0", "http-terminator": "^3.2.0", "ioredis": "^5.4.1", + "knex": "^2.5.1", "kafkajs": "2.2.4", "lru-memoizer": "2.3.0", "mongodb": "^3.7.3", diff --git a/dev-packages/node-integration-tests/suites/tracing/knex/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/knex/docker-compose.yml new file mode 100644 index 000000000000..50eb7bc94237 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/knex/docker-compose.yml @@ -0,0 +1,23 @@ +version: '3.9' + +services: + db_postgres: + image: postgres:13 + restart: always + container_name: integration-tests-knex-postgres + ports: + - '5445:5432' + environment: + POSTGRES_USER: test + POSTGRES_PASSWORD: test + POSTGRES_DB: tests + + db_mysql2: + image: mysql:8 + restart: always + container_name: integration-tests-knex-mysql2 + ports: + - '3307:3306' + environment: + MYSQL_ROOT_PASSWORD: docker + MYSQL_DATABASE: tests diff --git a/dev-packages/node-integration-tests/suites/tracing/knex/scenario-withMysql2.js b/dev-packages/node-integration-tests/suites/tracing/knex/scenario-withMysql2.js new file mode 100644 index 000000000000..5d57e38d9318 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/knex/scenario-withMysql2.js @@ -0,0 +1,53 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, + integrations: [Sentry.knexIntegration()], +}); + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +const knex = require('knex').default; + +const mysql2Client = knex({ + client: 'mysql2', + connection: { + host: 'localhost', + port: 3307, + user: 'root', + password: 'docker', + database: 'tests', + }, +}); + +async function run() { + await Sentry.startSpan( + { + name: 'Test Transaction', + op: 'transaction', + }, + async () => { + try { + await mysql2Client.schema.createTable('User', table => { + table.increments('id').notNullable().primary({ constraintName: 'User_pkey' }); + table.timestamp('createdAt', { precision: 3 }).notNullable().defaultTo(mysql2Client.fn.now(3)); + table.text('email').notNullable(); + table.text('name').notNullable(); + }); + + await mysql2Client('User').insert({ name: 'jane', email: 'jane@domain.com' }); + await mysql2Client('User').select('*'); + } finally { + await mysql2Client.destroy(); + } + }, + ); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/knex/scenario-withPostgres.js b/dev-packages/node-integration-tests/suites/tracing/knex/scenario-withPostgres.js new file mode 100644 index 000000000000..a9f2d558a618 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/knex/scenario-withPostgres.js @@ -0,0 +1,53 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, + integrations: [Sentry.knexIntegration()], +}); + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +const knex = require('knex').default; + +const pgClient = knex({ + client: 'pg', + connection: { + host: 'localhost', + port: 5445, + user: 'test', + password: 'test', + database: 'tests', + }, +}); + +async function run() { + await Sentry.startSpan( + { + name: 'Test Transaction', + op: 'transaction', + }, + async () => { + try { + await pgClient.schema.createTable('User', table => { + table.increments('id').notNullable().primary({ constraintName: 'User_pkey' }); + table.timestamp('createdAt', { precision: 3 }).notNullable().defaultTo(pgClient.fn.now(3)); + table.text('email').notNullable(); + table.text('name').notNullable(); + }); + + await pgClient('User').insert({ name: 'bob', email: 'bob@domain.com' }); + await pgClient('User').select('*'); + } finally { + await pgClient.destroy(); + } + }, + ); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/knex/test.ts b/dev-packages/node-integration-tests/suites/tracing/knex/test.ts new file mode 100644 index 000000000000..3ededda4f162 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/knex/test.ts @@ -0,0 +1,129 @@ +import { createRunner } from '../../../utils/runner'; + +// When running docker compose, we need a larger timeout, as this takes some time... +jest.setTimeout(90000); + +describe('knex auto instrumentation', () => { + // Update this if another knex version is installed + const KNEX_VERSION = '2.5.1'; + + test('should auto-instrument `knex` package when using `pg` client', done => { + const EXPECTED_TRANSACTION = { + transaction: 'Test Transaction', + spans: expect.arrayContaining([ + expect.objectContaining({ + data: expect.objectContaining({ + 'knex.version': KNEX_VERSION, + 'db.system': 'postgresql', + 'db.name': 'tests', + 'sentry.origin': 'auto.db.otel.knex', + 'sentry.op': 'db', + 'net.peer.name': 'localhost', + 'net.peer.port': 5445, + }), + status: 'ok', + description: + 'create table "User" ("id" serial primary key, "createdAt" timestamptz(3) not null default CURRENT_TIMESTAMP(3), "email" text not null, "name" text not null)', + origin: 'auto.db.otel.knex', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'knex.version': KNEX_VERSION, + 'db.system': 'postgresql', + 'db.name': 'tests', + 'sentry.origin': 'auto.db.otel.knex', + 'sentry.op': 'db', + 'net.peer.name': 'localhost', + 'net.peer.port': 5445, + }), + status: 'ok', + // In the knex-otel spans, the placeholders (e.g., `$1`) are replaced by a `?`. + description: 'insert into "User" ("email", "name") values (?, ?)', + origin: 'auto.db.otel.knex', + }), + + expect.objectContaining({ + data: expect.objectContaining({ + 'knex.version': KNEX_VERSION, + 'db.operation': 'select', + 'db.sql.table': 'User', + 'db.system': 'postgresql', + 'db.name': 'tests', + 'db.statement': 'select * from "User"', + 'sentry.origin': 'auto.db.otel.knex', + 'sentry.op': 'db', + }), + status: 'ok', + description: 'select * from "User"', + origin: 'auto.db.otel.knex', + }), + ]), + }; + + createRunner(__dirname, 'scenario-withPostgres.js') + .withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['port 5432'] }) + .expect({ transaction: EXPECTED_TRANSACTION }) + .start(done); + }); + + test('should auto-instrument `knex` package when using `mysql2` client', done => { + const EXPECTED_TRANSACTION = { + transaction: 'Test Transaction', + spans: expect.arrayContaining([ + expect.objectContaining({ + data: expect.objectContaining({ + 'knex.version': KNEX_VERSION, + 'db.system': 'mysql2', + 'db.name': 'tests', + 'db.user': 'root', + 'sentry.origin': 'auto.db.otel.knex', + 'sentry.op': 'db', + 'net.peer.name': 'localhost', + 'net.peer.port': 3307, + }), + status: 'ok', + description: + 'create table `User` (`id` int unsigned not null auto_increment primary key, `createdAt` timestamp(3) not null default CURRENT_TIMESTAMP(3), `email` text not null, `name` text not null)', + origin: 'auto.db.otel.knex', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'knex.version': KNEX_VERSION, + 'db.system': 'mysql2', + 'db.name': 'tests', + 'db.user': 'root', + 'sentry.origin': 'auto.db.otel.knex', + 'sentry.op': 'db', + 'net.peer.name': 'localhost', + 'net.peer.port': 3307, + }), + status: 'ok', + description: 'insert into `User` (`email`, `name`) values (?, ?)', + origin: 'auto.db.otel.knex', + }), + + expect.objectContaining({ + data: expect.objectContaining({ + 'knex.version': KNEX_VERSION, + 'db.operation': 'select', + 'db.sql.table': 'User', + 'db.system': 'mysql2', + 'db.name': 'tests', + 'db.statement': 'select * from `User`', + 'db.user': 'root', + 'sentry.origin': 'auto.db.otel.knex', + 'sentry.op': 'db', + }), + status: 'ok', + description: 'select * from `User`', + origin: 'auto.db.otel.knex', + }), + ]), + }; + + createRunner(__dirname, 'scenario-withMysql2.js') + .withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['port: 3306'] }) + .expect({ transaction: EXPECTED_TRANSACTION }) + .start(done); + }); +}); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index b2f57937148a..e4c871ec74ea 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -69,6 +69,7 @@ export { isInitialized, kafkaIntegration, koaIntegration, + knexIntegration, lastEventId, linkedErrorsIntegration, localVariablesIntegration, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index e6a21a498e2e..060dddd51787 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -92,6 +92,7 @@ export { fsIntegration, genericPoolIntegration, graphqlIntegration, + knexIntegration, kafkaIntegration, lruMemoizerIntegration, mongoIntegration, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 246f6ca9049c..5688d1007769 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -113,6 +113,7 @@ export { setupConnectErrorHandler, genericPoolIntegration, graphqlIntegration, + knexIntegration, kafkaIntegration, lruMemoizerIntegration, mongoIntegration, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index 5efe98ac5474..5e1c2bba5bc1 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -92,6 +92,7 @@ export { fastifyIntegration, genericPoolIntegration, graphqlIntegration, + knexIntegration, kafkaIntegration, lruMemoizerIntegration, mongoIntegration, diff --git a/packages/node/package.json b/packages/node/package.json index 8de05630c1b5..6e7a2a1660e1 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -9,9 +9,7 @@ "engines": { "node": ">=14.18" }, - "files": [ - "/build" - ], + "files": ["/build"], "main": "build/cjs/index.js", "module": "build/esm/index.js", "types": "build/types/index.d.ts", @@ -56,9 +54,7 @@ }, "typesVersions": { "<4.9": { - "build/types/index.d.ts": [ - "build/types-ts3.8/index.d.ts" - ] + "build/types/index.d.ts": ["build/types-ts3.8/index.d.ts"] } }, "publishConfig": { @@ -81,6 +77,7 @@ "@opentelemetry/instrumentation-http": "0.53.0", "@opentelemetry/instrumentation-ioredis": "0.43.0", "@opentelemetry/instrumentation-kafkajs": "0.4.0", + "@opentelemetry/instrumentation-knex": "0.41.0", "@opentelemetry/instrumentation-koa": "0.43.0", "@opentelemetry/instrumentation-lru-memoizer": "0.40.0", "@opentelemetry/instrumentation-mongodb": "0.48.0", diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 97694bee7c70..6ab536034894 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -28,6 +28,7 @@ export { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/h export { koaIntegration, setupKoaErrorHandler } from './integrations/tracing/koa'; export { connectIntegration, setupConnectErrorHandler } from './integrations/tracing/connect'; export { spotlightIntegration } from './integrations/spotlight'; +export { knexIntegration } from './integrations/tracing/knex'; export { tediousIntegration } from './integrations/tracing/tedious'; export { genericPoolIntegration } from './integrations/tracing/genericPool'; export { dataloaderIntegration } from './integrations/tracing/dataloader'; diff --git a/packages/node/src/integrations/tracing/knex.ts b/packages/node/src/integrations/tracing/knex.ts new file mode 100644 index 000000000000..55457680e101 --- /dev/null +++ b/packages/node/src/integrations/tracing/knex.ts @@ -0,0 +1,47 @@ +import { KnexInstrumentation } from '@opentelemetry/instrumentation-knex'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; +import { generateInstrumentOnce } from '../../otel/instrument'; + +const INTEGRATION_NAME = 'Knex'; + +export const instrumentKnex = generateInstrumentOnce( + INTEGRATION_NAME, + () => new KnexInstrumentation({ requireParentSpan: true }), +); + +const _knexIntegration = (() => { + return { + name: INTEGRATION_NAME, + setupOnce() { + instrumentKnex(); + }, + + setup(client) { + client.on('spanStart', span => { + const { data } = spanToJSON(span); + // knex.version is always set in the span data + // https://github.com/open-telemetry/opentelemetry-js-contrib/blob/0309caeafc44ac9cb13a3345b790b01b76d0497d/plugins/node/opentelemetry-instrumentation-knex/src/instrumentation.ts#L138 + if (data && 'knex.version' in data) { + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.knex'); + } + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Knex integration + * + * Capture tracing data for [Knex](https://knexjs.org/). + * + * @example + * ```javascript + * import * as Sentry from '@sentry/node'; + * + * Sentry.init({ + * integrations: [Sentry.knexIntegration()], + * }); + * ``` + */ +export const knexIntegration = defineIntegration(_knexIntegration); diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index d0ee1082426d..1291a3fb8767 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -68,6 +68,7 @@ export { inboundFiltersIntegration, initOpenTelemetry, isInitialized, + knexIntegration, kafkaIntegration, koaIntegration, lastEventId, diff --git a/packages/solidstart/src/server/index.ts b/packages/solidstart/src/server/index.ts index 21705110361b..54e0e8cf68ab 100644 --- a/packages/solidstart/src/server/index.ts +++ b/packages/solidstart/src/server/index.ts @@ -59,6 +59,7 @@ export { inboundFiltersIntegration, initOpenTelemetry, isInitialized, + knexIntegration, kafkaIntegration, koaIntegration, lastEventId, diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index bf317b964191..05f105c252a8 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -61,6 +61,7 @@ export { inboundFiltersIntegration, initOpenTelemetry, isInitialized, + knexIntegration, kafkaIntegration, koaIntegration, lastEventId, diff --git a/yarn.lock b/yarn.lock index f5a01a5a820c..da1c9aa37efc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7603,6 +7603,14 @@ "@opentelemetry/instrumentation" "^0.54.0" "@opentelemetry/semantic-conventions" "^1.27.0" +"@opentelemetry/instrumentation-knex@0.41.0": + version "0.41.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.41.0.tgz#74d611489e823003a825097bac019c6c2ad061a5" + integrity sha512-OhI1SlLv5qnsnm2dOVrian/x3431P75GngSpnR7c4fcVFv7prXGYu29Z6ILRWJf/NJt6fkbySmwdfUUnFnHCTg== + dependencies: + "@opentelemetry/instrumentation" "^0.54.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@opentelemetry/instrumentation-koa@0.43.0": version "0.43.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.43.0.tgz#963fd192a1b5f6cbae5dabf4ec82e3105cbb23b1" @@ -14464,6 +14472,11 @@ colord@^2.9.3: resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== +colorette@2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + colorette@^2.0.10: version "2.0.20" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" @@ -14511,7 +14524,7 @@ commander@7.2.0, commander@^7.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commander@^10.0.1: +commander@^10.0.0, commander@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== @@ -17885,7 +17898,7 @@ esm-env@^1.0.0: resolved "https://registry.yarnpkg.com/esm-env/-/esm-env-1.0.0.tgz#b124b40b180711690a4cb9b00d16573391950413" integrity sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA== -esm@^3.2.4: +esm@^3.2.25, esm@^3.2.4: version "3.2.25" resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== @@ -19196,6 +19209,11 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +getopts@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4" + integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA== + giget@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/giget/-/giget-1.2.3.tgz#ef6845d1140e89adad595f7f3bb60aa31c672cb6" @@ -20848,6 +20866,11 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== + invariant@^2.2.1, invariant@^2.2.2: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -22515,6 +22538,26 @@ klona@^2.0.4, klona@^2.0.5, klona@^2.0.6: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== +knex@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/knex/-/knex-2.5.1.tgz#a6c6b449866cf4229f070c17411f23871ba52ef9" + integrity sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA== + dependencies: + colorette "2.0.19" + commander "^10.0.0" + debug "4.3.4" + escalade "^3.1.1" + esm "^3.2.25" + get-package-type "^0.1.0" + getopts "2.3.0" + interpret "^2.2.0" + lodash "^4.17.21" + pg-connection-string "2.6.1" + rechoir "^0.8.0" + resolve-from "^5.0.0" + tarn "^3.0.2" + tildify "2.0.0" + knitwork@^1.0.0, knitwork@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/knitwork/-/knitwork-1.1.0.tgz#d8c9feafadd7ee744ff64340b216a52c7199c417" @@ -26821,6 +26864,11 @@ periscopic@^3.1.0: estree-walker "^3.0.0" is-reference "^3.0.0" +pg-connection-string@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.1.tgz#78c23c21a35dd116f48e12e23c0965e8d9e2cbfb" + integrity sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg== + pg-connection-string@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" @@ -28762,6 +28810,13 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== + dependencies: + resolve "^1.20.0" + redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -31476,6 +31531,11 @@ tar@^6.2.0: mkdirp "^1.0.3" yallist "^4.0.0" +tarn@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693" + integrity sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ== + tedious@^18.6.1: version "18.6.1" resolved "https://registry.yarnpkg.com/tedious/-/tedious-18.6.1.tgz#1c4a3f06c891be67a032117e2e25193286d44496" @@ -31692,6 +31752,11 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== +tildify@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a" + integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw== + tiny-glob@0.2.9, tiny-glob@^0.2.9: version "0.2.9" resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2" From 2e5fe94d40020668f801e59851be817148cfc6b7 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 12 Nov 2024 10:09:05 +0100 Subject: [PATCH 22/24] chore: Add external contributor to CHANGELOG.md (#14235) This PR adds the external contributor to the CHANGELOG.md file, so that they are credited for their contribution. See #13526 Co-authored-by: AbhiPrasad <18689448+AbhiPrasad@users.noreply.github.com> --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc925a41e558..1437756160fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +Work in this release was contributed by @Zen-cronic. Thank you for your contribution! + Work in this release was contributed by @grahamhency, @Zen-cronic, @gilisho and @phuctm97. Thank you for your contributions! ## 8.37.1 From 0aa7f1d8b44f452892cd6c6d18d41f3b2568b050 Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:52:36 +0100 Subject: [PATCH 23/24] fix(nuxt): Fix dynamic import rollup plugin to work with latest nitro (#14243) With nitro 2.10.X the way we imported the server config file and dynamically imported the nitro runtime broke. Instead of landing in the server output entry file, the sdk init and dynamic import landed in the nitro runtime itself. Based on https://github.com/nitrojs/nitro/commit/8b4a408231bdc222569a32ce109796a41eac4aa6#diff-e58102d2230f95ddeef2662957b48d847a6e891e354cfd0ae6e2e03ce848d1a2R142 we managed to get back to the original solution by using the `\0raw` prefix for the entry file. __Note for internal__: This does not play well when using symlinks (like `yarn link`) to link the sdk into a project. Haven't tested how yalc behaves, but tarballs work. --- packages/nuxt/src/vite/addServerConfig.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/nuxt/src/vite/addServerConfig.ts b/packages/nuxt/src/vite/addServerConfig.ts index cf4b2a95473e..824b46781e0d 100644 --- a/packages/nuxt/src/vite/addServerConfig.ts +++ b/packages/nuxt/src/vite/addServerConfig.ts @@ -118,6 +118,12 @@ function wrapEntryWithDynamicImport({ entrypointWrappedFunctions, debug, }: { resolvedSentryConfigPath: string; entrypointWrappedFunctions: string[]; debug?: boolean }): InputPluginOption { + // In order to correctly import the server config file + // and dynamically import the nitro runtime, we need to + // mark the resolutionId with '\0raw' to fall into the + // raw chunk group, c.f. https://github.com/nitrojs/nitro/commit/8b4a408231bdc222569a32ce109796a41eac4aa6#diff-e58102d2230f95ddeef2662957b48d847a6e891e354cfd0ae6e2e03ce848d1a2R142 + const resolutionIdPrefix = '\0raw'; + return { name: 'sentry-wrap-entry-with-dynamic-import', async resolveId(source, importer, options) { @@ -146,19 +152,19 @@ function wrapEntryWithDynamicImport({ // The enclosing `if` already checks for the suffix in `source`, but a check in `resolution.id` is needed as well to prevent multiple attachment of the suffix return resolution.id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`) ? resolution.id - : resolution.id + : `${resolutionIdPrefix}${resolution.id // Concatenates the query params to mark the file (also attaches names of re-exports - this is needed for serverless functions to re-export the handler) .concat(SENTRY_WRAPPED_ENTRY) .concat( constructWrappedFunctionExportQuery(moduleInfo.exportedBindings, entrypointWrappedFunctions, debug), ) - .concat(QUERY_END_INDICATOR); + .concat(QUERY_END_INDICATOR)}`; } return null; }, load(id: string) { if (id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)) { - const entryId = removeSentryQueryFromPath(id); + const entryId = removeSentryQueryFromPath(id).slice(resolutionIdPrefix.length); // Mostly useful for serverless `handler` functions const reExportedFunctions = From ad1f938f51beceee83d4d04039b7fe3b6ae2849b Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Tue, 12 Nov 2024 13:09:45 +0100 Subject: [PATCH 24/24] meta(changelog): Update changelog for 8.38.0 --- CHANGELOG.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1437756160fb..23375eb92a2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,23 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -Work in this release was contributed by @Zen-cronic. Thank you for your contribution! +## 8.38.0 + +- docs: Improve docstrings for node otel integrations ([#14217](https://github.com/getsentry/sentry-javascript/pull/14217)) +- feat(browser): Add moduleMetadataIntegration lazy loading support ([#13817](https://github.com/getsentry/sentry-javascript/pull/13817)) +- feat(core): Add trpc path to context in trpcMiddleware ([#14218](https://github.com/getsentry/sentry-javascript/pull/14218)) +- feat(deps): Bump @opentelemetry/instrumentation-amqplib from 0.42.0 to 0.43.0 ([#14230](https://github.com/getsentry/sentry-javascript/pull/14230)) +- feat(deps): Bump @sentry/cli from 2.37.0 to 2.38.2 ([#14232](https://github.com/getsentry/sentry-javascript/pull/14232)) +- feat(node): Add `knex` integration ([#13526](https://github.com/getsentry/sentry-javascript/pull/13526)) +- feat(node): Add `tedious` integration ([#13486](https://github.com/getsentry/sentry-javascript/pull/13486)) +- feat(utils): Single implementation to fetch debug ids ([#14199](https://github.com/getsentry/sentry-javascript/pull/14199)) +- fix(browser): Avoid recording long animation frame spans starting before their parent span ([#14186](https://github.com/getsentry/sentry-javascript/pull/14186)) +- fix(node): Include `debug_meta` with ANR events ([#14203](https://github.com/getsentry/sentry-javascript/pull/14203)) +- fix(nuxt): Fix dynamic import rollup plugin to work with latest nitro ([#14243](https://github.com/getsentry/sentry-javascript/pull/14243)) +- fix(react): Support wildcard routes on React Router 6 ([#14205](https://github.com/getsentry/sentry-javascript/pull/14205)) +- fix(spotlight): Export spotlightBrowserIntegration from the main browser package ([#14208](https://github.com/getsentry/sentry-javascript/pull/14208)) +- ref(browser): Ensure start time of interaction root and child span is aligned ([#14188](https://github.com/getsentry/sentry-javascript/pull/14188)) +- ref(nextjs): Make build-time value injection turbopack compatible ([#14081](https://github.com/getsentry/sentry-javascript/pull/14081)) Work in this release was contributed by @grahamhency, @Zen-cronic, @gilisho and @phuctm97. Thank you for your contributions!