From 730c79410081d864e3bd686dccda3f8d141cd4b9 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Wed, 29 May 2024 17:25:08 -0400 Subject: [PATCH 01/54] fix(node): Do not manually finish / update root Hapi spans. (#12287) We're currently attempting to manually update the Hapi route transaction status, followed by `.end()` call. Looking at `@opentelemetry/instrumentation-hapi`s code, it looks like that is [already covered by the underlying instrumentation](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/27d6503089163a3d1ed85687cd8e0bc43f525fdd/plugins/node/opentelemetry-instrumentation-hapi/src/instrumentation.ts#L395-L402). This PR removes that, also extends tests to make sure the error events and the transactions are linked properly. --- .../node-hapi/tests/errors.test.ts | 17 +++++++++++++++-- .../node/src/integrations/tracing/hapi/index.ts | 11 ----------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/node-hapi/tests/errors.test.ts index 7abe75892d19..c46265579a99 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-hapi/tests/errors.test.ts @@ -1,15 +1,25 @@ import { expect, test } from '@playwright/test'; -import { waitForError } from '@sentry-internal/event-proxy-server'; +import { waitForError, waitForTransaction } from '@sentry-internal/event-proxy-server'; test('Sends thrown error to Sentry', async ({ baseURL }) => { const errorEventPromise = waitForError('node-hapi', errorEvent => { return errorEvent?.exception?.values?.[0]?.value === 'This is an error'; }); + const transactionEventPromise = waitForTransaction('node-hapi', transactionEvent => { + return transactionEvent?.transaction === 'GET /test-failure'; + }); + await fetch(`${baseURL}/test-failure`); const errorEvent = await errorEventPromise; - const errorEventId = errorEvent.event_id; + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent.transaction).toBe('GET /test-failure'); + expect(transactionEvent.contexts?.trace).toMatchObject({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); expect(errorEvent.exception?.values).toHaveLength(1); expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an error'); @@ -27,6 +37,9 @@ test('Sends thrown error to Sentry', async ({ baseURL }) => { trace_id: expect.any(String), span_id: expect.any(String), }); + + expect(errorEvent.contexts?.trace?.trace_id).toBe(transactionEvent.contexts?.trace?.trace_id); + expect(errorEvent.contexts?.trace?.span_id).toBe(transactionEvent.contexts?.trace?.span_id); }); test('sends error with parameterized transaction name', async ({ baseURL }) => { diff --git a/packages/node/src/integrations/tracing/hapi/index.ts b/packages/node/src/integrations/tracing/hapi/index.ts index d197fbed0b2d..70c6fd51664b 100644 --- a/packages/node/src/integrations/tracing/hapi/index.ts +++ b/packages/node/src/integrations/tracing/hapi/index.ts @@ -3,14 +3,11 @@ import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SPAN_STATUS_ERROR, captureException, defineIntegration, - getActiveSpan, getClient, getDefaultIsolationScope, getIsolationScope, - getRootSpan, spanToJSON, } from '@sentry/core'; import type { IntegrationFn, Span } from '@sentry/types'; @@ -79,19 +76,11 @@ export const hapiErrorPlugin = { logger.warn('Isolation scope is still the default isolation scope - skipping setting transactionName'); } - const activeSpan = getActiveSpan(); - const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined; - if (request.response && isBoomObject(request.response)) { sendErrorToSentry(request.response); } else if (isErrorEvent(event)) { sendErrorToSentry(event.error); } - - if (rootSpan) { - rootSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); - rootSpan.end(); - } }); }, }; From 396112cd7d401559ec7950ba824094612fb5d857 Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Thu, 30 May 2024 21:25:29 +0200 Subject: [PATCH 02/54] feat(solidjs): Add solid router instrumentation (#12263) To use this integration: ```javascript import * as Sentry from "@sentry/solidjs"; import {Route, Router, useBeforeLeave, useLocation} from "@solidjs/router" Sentry.init({ dsn: integrations: [ Sentry.solidRouterBrowserTracingIntegration({ useBeforeLeave, useLocation }), ], tracesSampleRate: 1.0, // Capture 100% of the transactions debug: true, }); const SentryRouter = Sentry.withSentryRouterRouting(Router) render(() => ( // ... routes here // ), root!); ``` --- packages/solidjs/README.md | 41 +++++++ packages/solidjs/package.json | 7 +- packages/solidjs/src/debug-build.ts | 8 ++ packages/solidjs/src/index.ts | 2 + packages/solidjs/src/solidrouter.ts | 179 ++++++++++++++++++++++++++++ yarn.lock | 16 ++- 6 files changed, 244 insertions(+), 9 deletions(-) create mode 100644 packages/solidjs/src/debug-build.ts create mode 100644 packages/solidjs/src/solidrouter.ts diff --git a/packages/solidjs/README.md b/packages/solidjs/README.md index 3e37b30e7032..00d451763624 100644 --- a/packages/solidjs/README.md +++ b/packages/solidjs/README.md @@ -7,3 +7,44 @@ # Official Sentry SDK for SolidJS This SDK is work in progress, and should not be used before officially released. + +# Solid Router + +The Solid Router instrumentation uses the Solid Router library to create navigation spans to ensure you collect +meaningful performance data about the health of your page loads and associated requests. + +Add `Sentry.solidRouterBrowserTracingIntegration` instead of the regular `Sentry.browserTracingIntegration` and provide +the hooks it needs to enable performance tracing: + +`useBeforeLeave` from `@solidjs/router` +`useLocation` from `@solidjs/router` + +Make sure `Sentry.solidRouterBrowserTracingIntegration` is initialized by your `Sentry.init` call, before you wrap +`Router`. Otherwise, the routing instrumentation may not work properly. + +Wrap `Router`, `MemoryRouter` or `HashRouter` from `@solidjs/router` using `Sentry.withSentryRouterRouting`. This +creates a higher order component, which will enable Sentry to reach your router context. + +```js +import * as Sentry from '@sentry/solidjs'; +import { Route, Router, useBeforeLeave, useLocation } from '@solidjs/router'; + +Sentry.init({ + dsn: '__PUBLIC_DSN__', + integrations: [Sentry.solidRouterBrowserTracingIntegration({ useBeforeLeave, useLocation })], + tracesSampleRate: 1.0, // Capture 100% of the transactions + debug: true, +}); + +const SentryRouter = Sentry.withSentryRouterRouting(Router); + +render( + () => ( + + + ... + + ), + document.getElementById('root'), +); +``` diff --git a/packages/solidjs/package.json b/packages/solidjs/package.json index f3b91d7b706a..7b4317c79d60 100644 --- a/packages/solidjs/package.json +++ b/packages/solidjs/package.json @@ -44,14 +44,15 @@ "dependencies": { "@sentry/browser": "8.7.0", "@sentry/core": "8.7.0", - "@sentry/types": "8.7.0" + "@sentry/types": "8.7.0", + "@sentry/utils": "8.7.0" }, "peerDependencies": { - "solid-js": "1.8.x" + "solid-js": "^1.8.4" }, "devDependencies": { "@solidjs/testing-library": "0.8.5", - "solid-js": "1.8.11", + "solid-js": "^1.8.11", "vite-plugin-solid": "^2.8.2" }, "scripts": { diff --git a/packages/solidjs/src/debug-build.ts b/packages/solidjs/src/debug-build.ts new file mode 100644 index 000000000000..60aa50940582 --- /dev/null +++ b/packages/solidjs/src/debug-build.ts @@ -0,0 +1,8 @@ +declare const __DEBUG_BUILD__: boolean; + +/** + * This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code. + * + * ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking. + */ +export const DEBUG_BUILD = __DEBUG_BUILD__; diff --git a/packages/solidjs/src/index.ts b/packages/solidjs/src/index.ts index 8e25b84c4a0c..77f17110f5e1 100644 --- a/packages/solidjs/src/index.ts +++ b/packages/solidjs/src/index.ts @@ -1,3 +1,5 @@ export * from '@sentry/browser'; export { init } from './sdk'; + +export * from './solidrouter'; diff --git a/packages/solidjs/src/solidrouter.ts b/packages/solidjs/src/solidrouter.ts new file mode 100644 index 000000000000..d38b21313570 --- /dev/null +++ b/packages/solidjs/src/solidrouter.ts @@ -0,0 +1,179 @@ +import { + browserTracingIntegration, + getActiveSpan, + getRootSpan, + spanToJSON, + startBrowserTracingNavigationSpan, +} from '@sentry/browser'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + getClient, +} from '@sentry/core'; +import type { Client, Integration, Span } from '@sentry/types'; +import { logger } from '@sentry/utils'; +import { createEffect, mergeProps, splitProps } from 'solid-js'; +import type { Component, JSX, ParentProps } from 'solid-js'; +import { createComponent } from 'solid-js/web'; +import { DEBUG_BUILD } from './debug-build'; + +// Vendored solid router types so that we don't need to depend on solid router. +// These are not exhaustive and loose on purpose. +interface Location { + pathname: string; +} + +interface BeforeLeaveEventArgs { + from: Location; + to: string | number; +} + +interface RouteSectionProps { + location: Location; + data?: T; + children?: JSX.Element; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type RouteDefinition = { + path?: S; + children?: RouteDefinition | RouteDefinition[]; + component?: Component>; +}; + +interface RouterProps { + base?: string; + root?: Component; + children?: JSX.Element | RouteDefinition | RouteDefinition[]; +} + +interface SolidRouterOptions { + useBeforeLeave: UserBeforeLeave; + useLocation: UseLocation; +} + +type UserBeforeLeave = (listener: (e: BeforeLeaveEventArgs) => void) => void; +type UseLocation = () => Location; + +const CLIENTS_WITH_INSTRUMENT_NAVIGATION = new WeakSet(); + +let _useBeforeLeave: UserBeforeLeave; +let _useLocation: UseLocation; + +function handleNavigation(location: string): void { + const client = getClient(); + if (!client || !CLIENTS_WITH_INSTRUMENT_NAVIGATION.has(client)) { + return; + } + + startBrowserTracingNavigationSpan(client, { + name: location, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.solidjs.solidrouter', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + }, + }); +} + +function getActiveRootSpan(): Span | undefined { + const span = getActiveSpan(); + return span ? getRootSpan(span) : undefined; +} + +/** Pass-through component in case user didn't specify a root **/ +function SentryDefaultRoot(props: ParentProps): JSX.Element { + return props.children; +} + +/** + * Unfortunately, we cannot use router hooks directly in the Router, so we + * need to wrap the `root` prop to instrument navigation. + */ +function withSentryRouterRoot(Root: Component): Component { + const SentryRouterRoot = (props: RouteSectionProps): JSX.Element => { + // TODO: This is a rudimentary first version of handling navigation spans + // It does not + // - use query params + // - parameterize the route + + _useBeforeLeave(({ to }: BeforeLeaveEventArgs) => { + // `to` could be `-1` if the browser back-button was used + handleNavigation(to.toString()); + }); + + const location = _useLocation(); + createEffect(() => { + const name = location.pathname; + const rootSpan = getActiveRootSpan(); + + if (rootSpan) { + const { op, description } = spanToJSON(rootSpan); + + // We only need to update navigation spans that have been created by + // a browser back-button navigation (stored as `-1` by solid router) + // everything else was already instrumented correctly in `useBeforeLeave` + if (op === 'navigation' && description === '-1') { + rootSpan.updateName(name); + } + } + }); + + return createComponent(Root, props); + }; + + return SentryRouterRoot; +} + +/** + * A browser tracing integration that uses Solid Router to instrument navigations. + */ +export function solidRouterBrowserTracingIntegration( + options: Parameters[0] & SolidRouterOptions, +): Integration { + const integration = browserTracingIntegration({ + ...options, + instrumentNavigation: false, + }); + + const { instrumentNavigation = true, useBeforeLeave, useLocation } = options; + + return { + ...integration, + setup() { + _useBeforeLeave = useBeforeLeave; + _useLocation = useLocation; + }, + afterAllSetup(client) { + integration.afterAllSetup(client); + + if (instrumentNavigation) { + CLIENTS_WITH_INSTRUMENT_NAVIGATION.add(client); + } + }, + }; +} + +/** + * A higher-order component to instrument Solid Router to create navigation spans. + */ +export function withSentryRouterRouting(Router: Component): Component { + if (!_useBeforeLeave || !_useLocation) { + DEBUG_BUILD && + logger.warn(`solidRouterBrowserTracingIntegration was unable to wrap Solid Router because of one or more missing hooks. + useBeforeLeave: ${_useBeforeLeave}. useLocation: ${_useLocation}.`); + + return Router; + } + + const SentryRouter = (props: RouterProps): JSX.Element => { + const [local, others] = splitProps(props, ['root']); + // We need to wrap root here in case the user passed in their own root + const Root = withSentryRouterRoot(local.root ? local.root : SentryDefaultRoot); + + return createComponent(Router, mergeProps({ root: Root }, others)); + }; + + return SentryRouter; +} diff --git a/yarn.lock b/yarn.lock index e0e07a2aefae..e054994642fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8452,6 +8452,7 @@ "@types/unist" "*" "@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8", "@types/history@*": + name "@types/history-4" version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== @@ -26109,6 +26110,7 @@ react-is@^18.0.0: "@remix-run/router" "1.0.2" "react-router-6@npm:react-router@6.3.0", react-router@6.3.0: + name react-router-6 version "6.3.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== @@ -27492,7 +27494,7 @@ seroval-plugins@^1.0.3: resolved "https://registry.yarnpkg.com/seroval-plugins/-/seroval-plugins-1.0.7.tgz#c02511a1807e9bc8f68a91fbec13474fa9cea670" integrity sha512-GO7TkWvodGp6buMEX9p7tNyIkbwlyuAWbI6G9Ec5bhcm7mQdu3JOK1IXbEUwb3FVzSc363GraG/wLW23NSavIw== -seroval@^1.0.3: +seroval@^1.0.4: version "1.0.7" resolved "https://registry.yarnpkg.com/seroval/-/seroval-1.0.7.tgz#ee48ad8ba69f1595bdd5c55d1a0d1da29dee7455" integrity sha512-n6ZMQX5q0Vn19Zq7CIKNIo7E75gPkGCFUEqDpa8jgwpYr/vScjqnQ6H09t1uIiZ0ZSK0ypEGvrYK2bhBGWsGdw== @@ -27934,13 +27936,13 @@ socks@^2.6.2: ip "^2.0.0" smart-buffer "^4.2.0" -solid-js@1.8.11: - version "1.8.11" - resolved "https://registry.yarnpkg.com/solid-js/-/solid-js-1.8.11.tgz#0e7496a9834720b10fe739eaac250221d3f72cd5" - integrity sha512-WdwmER+TwBJiN4rVQTVBxocg+9pKlOs41KzPYntrC86xO5sek8TzBYozPEZPL1IRWDouf2lMrvSbIs3CanlPvQ== +solid-js@^1.8.11: + version "1.8.17" + resolved "https://registry.yarnpkg.com/solid-js/-/solid-js-1.8.17.tgz#780ed6f0fd8633009d1b3c29d56bf6b6bb33bd50" + integrity sha512-E0FkUgv9sG/gEBWkHr/2XkBluHb1fkrHywUgA6o6XolPDCJ4g1HaLmQufcBBhiF36ee40q+HpG/vCZu7fLpI3Q== dependencies: csstype "^3.1.0" - seroval "^1.0.3" + seroval "^1.0.4" seroval-plugins "^1.0.3" solid-refresh@^0.6.3: @@ -28442,6 +28444,7 @@ string-template@~0.2.1: integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: + name string-width-cjs version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -31190,6 +31193,7 @@ workerpool@^6.4.0: integrity sha512-i3KR1mQMNwY2wx20ozq2EjISGtQWDIfV56We+yGJ5yDs8jTwQiLLaqHlkBHITlCuJnYlVRmXegxFxZg7gqI++A== "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + name wrap-ansi-cjs version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From b4332c1e9db9754e2258a907ffe47893975b7ca5 Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Thu, 30 May 2024 13:53:42 -0700 Subject: [PATCH 03/54] fix(feedback): Wait for document to be ready before doing autoinject (#12294) The error that folks are getting is something like this: `[Error] TypeError: null is not an object (evaluating 'qo.body.appendChild')` The problem seems to be that we're trying to inject the button into the html before the `` is ready. Using async ` + + diff --git a/dev-packages/e2e-tests/test-applications/solidjs/package.json b/dev-packages/e2e-tests/test-applications/solidjs/package.json new file mode 100644 index 000000000000..1922a6e26d17 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/package.json @@ -0,0 +1,33 @@ +{ + "name": "solidjs", + "version": "0.0.0", + "description": "", + "scripts": { + "build": "vite build", + "clean": "npx rimraf node_modules pnpm-lock.yaml dist", + "dev": "vite", + "preview": "vite preview", + "start": "vite", + "test:prod": "TEST_ENV=production playwright test", + "test:build": "pnpm install && npx playwright install && pnpm build", + "test:assert": "pnpm test:prod" + }, + "license": "MIT", + "devDependencies": { + "@playwright/test": "^1.44.1", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@sentry/types": "latest || *", + "@sentry/utils": "latest || *", + "autoprefixer": "^10.4.17", + "postcss": "^8.4.33", + "solid-devtools": "^0.29.2", + "tailwindcss": "^3.4.1", + "vite": "^5.0.11", + "vite-plugin-solid": "^2.8.2" + }, + "dependencies": { + "@solidjs/router": "^0.13.5", + "solid-js": "^1.8.11", + "@sentry/solidjs": "latest || *" + } +} diff --git a/dev-packages/e2e-tests/test-applications/solidjs/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/solidjs/playwright.config.mjs new file mode 100644 index 000000000000..0c468af7d879 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/playwright.config.mjs @@ -0,0 +1,8 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: 'pnpm preview --port 3030', + port: 3030, +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/solidjs/postcss.config.js b/dev-packages/e2e-tests/test-applications/solidjs/postcss.config.js new file mode 100644 index 000000000000..12a703d900da --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/dev-packages/e2e-tests/test-applications/solidjs/src/errors/404.tsx b/dev-packages/e2e-tests/test-applications/solidjs/src/errors/404.tsx new file mode 100644 index 000000000000..56e5ad5e3be0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/src/errors/404.tsx @@ -0,0 +1,8 @@ +export default function NotFound() { + return ( +
+

404: Not Found

+

It's gone 😞

+
+ ); +} diff --git a/dev-packages/e2e-tests/test-applications/solidjs/src/index.css b/dev-packages/e2e-tests/test-applications/solidjs/src/index.css new file mode 100644 index 000000000000..b5c61c956711 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/dev-packages/e2e-tests/test-applications/solidjs/src/index.tsx b/dev-packages/e2e-tests/test-applications/solidjs/src/index.tsx new file mode 100644 index 000000000000..47e7c0e52904 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/src/index.tsx @@ -0,0 +1,21 @@ +/* @refresh reload */ +import * as Sentry from '@sentry/solidjs'; +import { Router, useBeforeLeave, useLocation } from '@solidjs/router'; +import { render } from 'solid-js/web'; +import './index.css'; +import PageRoot from './pageroot'; +import { routes } from './routes'; + +Sentry.init({ + dsn: import.meta.env.PUBLIC_E2E_TEST_DSN, + debug: true, + environment: 'qa', // dynamic sampling bias to keep transactions + integrations: [Sentry.solidRouterBrowserTracingIntegration({ useBeforeLeave, useLocation })], + release: 'e2e-test', + tunnel: 'http://localhost:3031/', // proxy server + tracesSampleRate: 1.0, +}); + +const SentryRouter = Sentry.withSentryRouterRouting(Router); + +render(() => {routes}, document.getElementById('root')); diff --git a/dev-packages/e2e-tests/test-applications/solidjs/src/pageroot.tsx b/dev-packages/e2e-tests/test-applications/solidjs/src/pageroot.tsx new file mode 100644 index 000000000000..d9770c8a3868 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/src/pageroot.tsx @@ -0,0 +1,23 @@ +import { A } from '@solidjs/router'; + +export default function PageRoot(props) { + return ( + <> + +
{props.children}
+ + ); +} diff --git a/dev-packages/e2e-tests/test-applications/solidjs/src/pages/home.tsx b/dev-packages/e2e-tests/test-applications/solidjs/src/pages/home.tsx new file mode 100644 index 000000000000..7500846f0555 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/src/pages/home.tsx @@ -0,0 +1,39 @@ +import { A } from '@solidjs/router'; +import { createSignal } from 'solid-js'; + +export default function Home() { + const [count, setCount] = createSignal(0); + + return ( +
+

Home

+

This is the home page.

+ +
+ + + Count: {count()} + + +
+
+ + + User 5 + +
+
+ ); +} diff --git a/dev-packages/e2e-tests/test-applications/solidjs/src/pages/user.tsx b/dev-packages/e2e-tests/test-applications/solidjs/src/pages/user.tsx new file mode 100644 index 000000000000..639ab0be8118 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/src/pages/user.tsx @@ -0,0 +1,6 @@ +import { useParams } from '@solidjs/router'; + +export default function User() { + const params = useParams(); + return
User ID: {params.id}
; +} diff --git a/dev-packages/e2e-tests/test-applications/solidjs/src/routes.ts b/dev-packages/e2e-tests/test-applications/solidjs/src/routes.ts new file mode 100644 index 000000000000..7b115f68c00c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/src/routes.ts @@ -0,0 +1,18 @@ +import { lazy } from 'solid-js'; + +import Home from './pages/home'; + +export const routes = [ + { + path: '/', + component: Home, + }, + { + path: '/user/:id', + component: lazy(() => import('./pages/user')), + }, + { + path: '**', + component: lazy(() => import('./errors/404')), + }, +]; diff --git a/dev-packages/e2e-tests/test-applications/solidjs/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/solidjs/start-event-proxy.mjs new file mode 100644 index 000000000000..207afe3f56e1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'solidjs', +}); diff --git a/dev-packages/e2e-tests/test-applications/solidjs/tailwind.config.ts b/dev-packages/e2e-tests/test-applications/solidjs/tailwind.config.ts new file mode 100644 index 000000000000..f69a95185570 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/tailwind.config.ts @@ -0,0 +1,11 @@ +import type { Config } from 'tailwindcss'; + +const config: Config = { + content: ['./src/**/*.{js,jsx,ts,tsx}'], + theme: { + extend: {}, + }, + plugins: [], +}; + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/solidjs/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/solidjs/tests/errors.test.ts new file mode 100644 index 000000000000..92618f628407 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/tests/errors.test.ts @@ -0,0 +1,28 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/test-utils'; + +test('sends an error', async ({ page }) => { + const errorPromise = waitForError('solidjs', async errorEvent => { + return !errorEvent.type; + }); + + await Promise.all([page.goto(`/`), page.locator('#errorBtn').click()]); + + const error = await errorPromise; + + expect(error).toMatchObject({ + exception: { + values: [ + { + type: 'Error', + value: 'Error thrown from SolidJS E2E test app', + mechanism: { + type: 'onerror', + handled: false, + }, + }, + ], + }, + transaction: '/', + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/solidjs/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/solidjs/tests/performance.test.ts new file mode 100644 index 000000000000..166dfe01d32b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/tests/performance.test.ts @@ -0,0 +1,91 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('sends a pageload transaction', async ({ page }) => { + const transactionPromise = waitForTransaction('solidjs', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const [, pageloadTransaction] = await Promise.all([page.goto('/'), transactionPromise]); + + expect(pageloadTransaction).toMatchObject({ + contexts: { + trace: { + op: 'pageload', + origin: 'auto.pageload.browser', + }, + }, + transaction: '/', + transaction_info: { + source: 'url', + }, + }); +}); + +test('sends a navigation transaction', async ({ page }) => { + const transactionPromise = waitForTransaction('solidjs', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/`); + + const [, navigationTransaction] = await Promise.all([page.locator('#navLink').click(), transactionPromise]); + + expect(navigationTransaction).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.solidjs.solidrouter', + }, + }, + transaction: '/user/5', + transaction_info: { + source: 'url', + }, + }); +}); + +test('updates the transaction when using the back button', async ({ page }) => { + // Solid Router sends a `-1` navigation when using the back button. + // The sentry solidRouterBrowserTracingIntegration tries to update such + // transactions with the proper name once the `useLocation` hook triggers. + const navigationTxnPromise = waitForTransaction('solidjs', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/`); + + const [, navigationTxn] = await Promise.all([page.locator('#navLink').click(), navigationTxnPromise]); + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.solidjs.solidrouter', + }, + }, + transaction: '/user/5', + transaction_info: { + source: 'url', + }, + }); + + const backNavigationTxnPromise = waitForTransaction('solidjs', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + const [, backNavigationTxn] = await Promise.all([page.goBack(), backNavigationTxnPromise]); + + expect(backNavigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.solidjs.solidrouter', + }, + }, + transaction: '/', + transaction_info: { + source: 'url', + }, + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/solidjs/tsconfig.json b/dev-packages/e2e-tests/test-applications/solidjs/tsconfig.json new file mode 100644 index 000000000000..ba12e96f069a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "types": ["vite/client"], + "noEmit": true, + "isolatedModules": true, + }, +} diff --git a/dev-packages/e2e-tests/test-applications/solidjs/vite.config.ts b/dev-packages/e2e-tests/test-applications/solidjs/vite.config.ts new file mode 100644 index 000000000000..d1835ee1b8ff --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidjs/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite'; +import solidPlugin from 'vite-plugin-solid'; + +export default defineConfig({ + plugins: [solidPlugin()], + build: { + target: 'esnext', + }, + envPrefix: 'PUBLIC_', +}); From 6d83a0a5183d084fbcc2f249542be5d6aa79aed2 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 4 Jun 2024 10:25:57 +0200 Subject: [PATCH 24/54] ci(node): Add size limit entry for node without performance (#12342) You can initialize the Node SDK like this: ```js import * as Node from '@sentry/node'; Sentry.initWithoutDefaultIntegrations({ dsn: '...', integrations: [...Sentry.getDefaultIntegrationsWithoutPerformance()] }); ``` Which can be used to tree shake all the otel performance instrumentation (except for http) away. Also adjusts the size limits to be the current values + ~15kb. --- .size-limit.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index 8a55b86e9b5e..0784ca597a7c 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -211,7 +211,24 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '180 KB', + limit: '130 KB', + }, + { + name: '@sentry/node - without tracing', + path: 'packages/node/build/esm/index.js', + import: createImport('initWithoutDefaultIntegrations', 'getDefaultIntegrationsWithoutPerformance'), + gzip: true, + limit: '110 KB', + ignore: [...builtinModules, ...nodePrefixedBuiltinModules], + modifyWebpackConfig: function (config) { + const webpack = require('webpack'); + config.plugins.push( + new webpack.DefinePlugin({ + __SENTRY_TRACING__: false, + }), + ); + return config; + }, }, // AWS SDK (ESM) { @@ -220,7 +237,7 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '140 KB', + limit: '120 KB', }, ]; From b85f88e9e3d7ab1533340cb227b6d6d7a4c17861 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Tue, 4 Jun 2024 10:30:43 +0200 Subject: [PATCH 25/54] ci(canary-tests): Fix Next.js canary test issues (#12326) --- .github/workflows/canary.yml | 4 ++-- .../e2e-tests/test-applications/nextjs-15/package.json | 4 ++-- .../test-applications/nextjs-app-dir/assert-build.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index a9bd341b15b8..b8cffb3698ea 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -54,7 +54,7 @@ jobs: name: E2E ${{ matrix.label }} Test needs: [job_e2e_prepare] runs-on: ubuntu-20.04 - timeout-minutes: 15 + timeout-minutes: 20 env: E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} @@ -138,7 +138,7 @@ jobs: - name: Run E2E test working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - timeout-minutes: 5 + timeout-minutes: 15 run: yarn test:assert - name: Create Issue diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json index 3c39c0331b50..ebd18c6fb10e 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json @@ -8,8 +8,8 @@ "test:prod": "TEST_ENV=production playwright test", "test:dev": "TEST_ENV=development playwright test", "test:build": "pnpm install && npx playwright install && pnpm build", - "test:build-canary": "pnpm install && pnpm add next@canary && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build", - "test:build-latest": "pnpm install && pnpm add next@latest && npx playwright install && pnpm build", + "test:build-canary": "pnpm install && pnpm add next@rc && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build", + "test:build-latest": "pnpm install && pnpm add next@rc && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build", "test:assert": "pnpm test:prod && pnpm test:dev" }, "dependencies": { diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts index 2129f75d6b16..0c072c367e53 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/assert-build.ts @@ -8,7 +8,7 @@ const buildStdout = fs.readFileSync('.tmp_build_stdout', 'utf-8'); const buildStderr = fs.readFileSync('.tmp_build_stderr', 'utf-8'); // Assert that there was no funky build time warning when we are on a stable (pinned) version -if (nextjsVersion !== 'latest' && nextjsVersion !== 'canary') { +if (nextjsVersion !== 'latest' && !nextjsVersion.includes('-canary') && !nextjsVersion.includes('-rc')) { assert.doesNotMatch(buildStderr, /Import trace for requested module/); // This is Next.js/Webpack speech for "something is off" } From 0d9856e4cf4fec23e56fccb32344fffd91ea90bc Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Tue, 4 Jun 2024 14:06:21 +0200 Subject: [PATCH 26/54] feat(redis): Support `mget` command in caching functionality --- .../tracing/redis-cache/scenario-ioredis.js | 2 + .../suites/tracing/redis-cache/test.ts | 31 +++- .../node/src/integrations/tracing/redis.ts | 69 +++----- packages/node/src/utils/redisCache.ts | 77 +++++++++ .../test/integrations/tracing/redis.test.ts | 152 ++++++++++++++++++ .../src/utils/parseSpanDescription.ts | 9 +- 6 files changed, 286 insertions(+), 54 deletions(-) create mode 100644 packages/node/src/utils/redisCache.ts create mode 100644 packages/node/test/integrations/tracing/redis.test.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache/scenario-ioredis.js b/dev-packages/node-integration-tests/suites/tracing/redis-cache/scenario-ioredis.js index 22385f81b064..18f06dc67c29 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache/scenario-ioredis.js +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache/scenario-ioredis.js @@ -30,6 +30,8 @@ async function run() { await redis.get('test-key'); await redis.get('ioredis-cache:test-key'); await redis.get('ioredis-cache:unavailable-data'); + + await redis.mget('test-key', 'ioredis-cache:test-key', 'ioredis-cache:unavailable-data'); } finally { await redis.disconnect(); } diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts index 3ad860bb72f4..740c85e6d6e0 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts @@ -48,11 +48,12 @@ describe('redis cache auto instrumentation', () => { spans: expect.arrayContaining([ // SET expect.objectContaining({ - description: 'set ioredis-cache:test-key [1 other arguments]', + description: 'ioredis-cache:test-key', op: 'cache.put', origin: 'auto.db.otel.redis', data: expect.objectContaining({ - 'db.statement': 'set ioredis-cache:test-key [1 other arguments]', + 'sentry.origin': 'auto.db.otel.redis', + 'db.statement': 'ioredis-cache:test-key [1 other arguments]', 'cache.key': 'ioredis-cache:test-key', 'cache.item_size': 2, 'network.peer.address': 'localhost', @@ -61,10 +62,11 @@ describe('redis cache auto instrumentation', () => { }), // GET expect.objectContaining({ - description: 'get ioredis-cache:test-key', - op: 'cache.get_item', // todo: will be changed to cache.get + description: 'ioredis-cache:test-key', + op: 'cache.get', origin: 'auto.db.otel.redis', data: expect.objectContaining({ + 'sentry.origin': 'auto.db.otel.redis', 'db.statement': 'get ioredis-cache:test-key', 'cache.hit': true, 'cache.key': 'ioredis-cache:test-key', @@ -73,12 +75,13 @@ describe('redis cache auto instrumentation', () => { 'network.peer.port': 6379, }), }), - // GET (unavailable) + // GET (unavailable - no cache hit) expect.objectContaining({ - description: 'get ioredis-cache:unavailable-data', - op: 'cache.get_item', // todo: will be changed to cache.get + description: 'ioredis-cache:unavailable-data', + op: 'cache.get', origin: 'auto.db.otel.redis', data: expect.objectContaining({ + 'sentry.origin': 'auto.db.otel.redis', 'db.statement': 'get ioredis-cache:unavailable-data', 'cache.hit': false, 'cache.key': 'ioredis-cache:unavailable-data', @@ -86,6 +89,20 @@ describe('redis cache auto instrumentation', () => { 'network.peer.port': 6379, }), }), + // MGET + expect.objectContaining({ + description: 'test-key,ioredis-cache:test-key,ioredis-cache:unavailable-data', + op: 'cache.get', + origin: 'auto.db.otel.redis', + data: expect.objectContaining({ + 'sentry.origin': 'auto.db.otel.redis', + 'db.statement': 'mget [3 other arguments]', + 'cache.hit': true, + 'cache.key': 'test-key,ioredis-cache:test-key,ioredis-cache:unavailable-data', + 'network.peer.address': 'localhost', + 'network.peer.port': 6379, + }), + }), ]), }; diff --git a/packages/node/src/integrations/tracing/redis.ts b/packages/node/src/integrations/tracing/redis.ts index 1379336412f6..0a3924e98412 100644 --- a/packages/node/src/integrations/tracing/redis.ts +++ b/packages/node/src/integrations/tracing/redis.ts @@ -10,32 +10,12 @@ import { } from '@sentry/core'; import type { IntegrationFn } from '@sentry/types'; import { generateInstrumentOnce } from '../../otel/instrument'; - -function keyHasPrefix(key: string, prefixes: string[]): boolean { - return prefixes.some(prefix => key.startsWith(prefix)); -} - -/** Currently, caching only supports 'get' and 'set' commands. More commands will be added (setex, mget, del, expire) */ -function shouldConsiderForCache( - redisCommand: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - key: string | number | any[] | Buffer, - prefixes: string[], -): boolean { - return (redisCommand === 'get' || redisCommand === 'set') && typeof key === 'string' && keyHasPrefix(key, prefixes); -} - -function calculateCacheItemSize(response: unknown): number | undefined { - try { - if (Buffer.isBuffer(response)) return response.byteLength; - else if (typeof response === 'string') return response.length; - else if (typeof response === 'number') return response.toString().length; - else if (response === null || response === undefined) return 0; - return JSON.stringify(response).length; - } catch (e) { - return undefined; - } -} +import { + calculateCacheItemSize, + getCacheKeySafely, + getCacheOperation, + shouldConsiderForCache, +} from '../../utils/redisCache'; interface RedisOptions { cachePrefixes?: string[]; @@ -48,11 +28,14 @@ let _redisOptions: RedisOptions = {}; export const instrumentRedis = generateInstrumentOnce(INTEGRATION_NAME, () => { return new IORedisInstrumentation({ responseHook: (span, redisCommand, cmdArgs, response) => { - const key = cmdArgs[0]; + const safeKey = getCacheKeySafely(cmdArgs); span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.redis'); - if (!_redisOptions?.cachePrefixes || !shouldConsiderForCache(redisCommand, key, _redisOptions.cachePrefixes)) { + if ( + !_redisOptions?.cachePrefixes || + !shouldConsiderForCache(redisCommand, safeKey, _redisOptions.cachePrefixes) + ) { // not relevant for cache return; } @@ -65,26 +48,22 @@ export const instrumentRedis = generateInstrumentOnce(INTEGRATION_NAME, () => { span.setAttributes({ 'network.peer.address': networkPeerAddress, 'network.peer.port': networkPeerPort }); } + const cacheOperation = getCacheOperation(redisCommand); + + if (!cacheOperation) return; // redis command unsupported as cache operation + const cacheItemSize = calculateCacheItemSize(response); if (cacheItemSize) span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_ITEM_SIZE, cacheItemSize); - if (typeof key === 'string') { - switch (redisCommand) { - case 'get': - span.setAttributes({ - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.get_item', // todo: will be changed to cache.get - [SEMANTIC_ATTRIBUTE_CACHE_KEY]: key, - }); - if (cacheItemSize !== undefined) span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_HIT, cacheItemSize > 0); - break; - case 'set': - span.setAttributes({ - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.put', - [SEMANTIC_ATTRIBUTE_CACHE_KEY]: key, - }); - break; - } - } + if (cacheOperation === 'cache.get' && cacheItemSize !== undefined) + span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_HIT, cacheItemSize > 0); + + span.setAttributes({ + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: cacheOperation, + [SEMANTIC_ATTRIBUTE_CACHE_KEY]: safeKey, + }); + + span.updateName(`${safeKey.substring(0, 1024)}...`); }, }); }); diff --git a/packages/node/src/utils/redisCache.ts b/packages/node/src/utils/redisCache.ts new file mode 100644 index 000000000000..bbb7b302f4a8 --- /dev/null +++ b/packages/node/src/utils/redisCache.ts @@ -0,0 +1,77 @@ +import type { CommandArgs as IORedisCommandArgs } from '@opentelemetry/instrumentation-ioredis'; + +export const GET_COMMANDS = ['get', 'mget']; +export const SET_COMMANDS = ['set' /* todo: 'setex' */]; +// todo: del, expire + +/** Determine cache operation based on redis statement */ +export function getCacheOperation( + statement: string, +): 'cache.get' | 'cache.put' | 'cache.remove' | 'cache.flush' | undefined { + const lowercaseStatement = statement.toLowerCase(); + + if (GET_COMMANDS.includes(lowercaseStatement)) { + return 'cache.get'; + } else if (SET_COMMANDS.includes(lowercaseStatement)) { + return 'cache.put'; + } else { + return undefined; + } +} + +function keyHasPrefix(key: string, prefixes: string[]): boolean { + return prefixes.some(prefix => key.startsWith(prefix)); +} + +/** Safely converts a redis key to a string (comma-separated if there are multiple keys) */ +export function getCacheKeySafely(cmdArgs: IORedisCommandArgs): string { + try { + if (cmdArgs.length === 0) { + return ''; + } + + const keys: string[] = []; + + cmdArgs.forEach(arg => { + if (typeof arg === 'string' || typeof arg === 'number') { + keys.push(arg.toString()); + } else if (Buffer.isBuffer(arg)) { + keys.push(arg.toString()); + } else if (Array.isArray(arg)) { + arg.forEach(subArg => { + if (typeof subArg === 'string' || typeof subArg === 'number') { + keys.push(subArg.toString()); + } else if (Buffer.isBuffer(subArg)) { + keys.push(subArg.toString()); + } + }); + } + }); + + return keys.join(','); + } catch (e) { + return ''; + } +} + +/** Determines whether a redis operation should be considered as "cache operation" by checking if a key is prefixed. + * We only support certain commands (such as 'set', 'get', 'mget'). */ +export function shouldConsiderForCache(redisCommand: string, key: string, prefixes: string[]): boolean { + const lowercaseCommand = redisCommand.toLowerCase(); + if (!SET_COMMANDS.includes(lowercaseCommand) && !GET_COMMANDS.includes(lowercaseCommand)) return false; + + return key.split(',').reduce((prev, key) => prev || keyHasPrefix(key, prefixes), false); +} + +/** Calculates size based on the cache response value */ +export function calculateCacheItemSize(response: unknown): number | undefined { + try { + if (Buffer.isBuffer(response)) return response.byteLength; + else if (typeof response === 'string') return response.length; + else if (typeof response === 'number') return response.toString().length; + else if (response === null || response === undefined) return 0; + return JSON.stringify(response).length; + } catch (e) { + return undefined; + } +} diff --git a/packages/node/test/integrations/tracing/redis.test.ts b/packages/node/test/integrations/tracing/redis.test.ts new file mode 100644 index 000000000000..ab226f9e7358 --- /dev/null +++ b/packages/node/test/integrations/tracing/redis.test.ts @@ -0,0 +1,152 @@ +import { + GET_COMMANDS, + SET_COMMANDS, + calculateCacheItemSize, + getCacheKeySafely, + shouldConsiderForCache, +} from '../../../src/utils/redisCache'; + +describe('Redis', () => { + describe('getCacheKeySafely', () => { + it('should return an empty string if there are no command arguments', () => { + const result = getCacheKeySafely([]); + expect(result).toBe(''); + }); + + it('should return a string representation of a single argument', () => { + const cmdArgs = ['key1']; + const result = getCacheKeySafely(cmdArgs); + expect(result).toBe('key1'); + }); + + it('should return a comma-separated string for multiple arguments', () => { + const cmdArgs = ['key1', 'key2', 'key3']; + const result = getCacheKeySafely(cmdArgs); + expect(result).toBe('key1,key2,key3'); + }); + + it('should handle number arguments', () => { + const cmdArgs = [1, 2, 3]; + const result = getCacheKeySafely(cmdArgs); + expect(result).toBe('1,2,3'); + }); + + it('should handle Buffer arguments', () => { + const cmdArgs = [Buffer.from('key1'), Buffer.from('key2')]; + const result = getCacheKeySafely(cmdArgs); + expect(result).toBe('key1,key2'); + }); + + it('should handle array arguments', () => { + const cmdArgs = [ + ['key1', 'key2'], + ['key3', 'key4'], + ]; + const result = getCacheKeySafely(cmdArgs); + expect(result).toBe('key1,key2,key3,key4'); + }); + + it('should handle mixed type arguments', () => { + const cmdArgs = [Buffer.from('key1'), ['key2', 'key3'], [Buffer.from('key4'), 'key5', 'key6', 7]]; + const result = getCacheKeySafely(cmdArgs); + expect(result).toBe('key1,key2,key3,key4,key5,key6,7'); + }); + + it('should return an empty string if an error occurs', () => { + const cmdArgs = [Symbol('key1')]; // Symbols cannot be converted to a string + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const result = getCacheKeySafely(cmdArgs); + expect(result).toBe(''); + }); + }); + + describe('calculateCacheItemSize', () => { + it('should return byte length if response is a Buffer', () => { + const response = Buffer.from('test'); + const result = calculateCacheItemSize(response); + expect(result).toBe(response.byteLength); + }); + + it('should return string length if response is a string', () => { + const response = 'test'; + const result = calculateCacheItemSize(response); + expect(result).toBe(response.length); + }); + + it('should return length of string representation if response is a number', () => { + const response = 1234; + const result = calculateCacheItemSize(response); + expect(result).toBe(response.toString().length); + }); + + it('should return 0 if response is null or undefined', () => { + const response = null; + const result = calculateCacheItemSize(response); + expect(result).toBe(0); + }); + + it('should return length of JSON stringified response if response is an object', () => { + const response = { key: 'value' }; + const result = calculateCacheItemSize(response); + expect(result).toBe(JSON.stringify(response).length); + }); + + it('should return undefined if an error occurs', () => { + const circularObject: { self?: any } = {}; + circularObject.self = circularObject; // This will cause JSON.stringify to throw an error + const result = calculateCacheItemSize(circularObject); + expect(result).toBeUndefined(); + }); + }); + + describe('shouldConsiderForCache', () => { + const prefixes = ['cache:', 'ioredis-cache:']; + + it('should return false for non-cache commands', () => { + const command = 'EXISTS'; + const commandLowercase = 'exists'; + const key = 'cache:test-key'; + const result1 = shouldConsiderForCache(command, key, prefixes); + const result2 = shouldConsiderForCache(commandLowercase, key, prefixes); + expect(result1).toBe(false); + expect(result2).toBe(false); + }); + + it('should return true for cache commands with matching prefix', () => { + const command = 'get'; + const key = 'cache:test-key'; + const result = shouldConsiderForCache(command, key, prefixes); + expect(result).toBe(true); + }); + + it('should return false for cache commands without matching prefix', () => { + const command = 'get'; + const key = 'test-key'; + const result = shouldConsiderForCache(command, key, prefixes); + expect(result).toBe(false); + }); + + it('should return true for multiple keys with at least one matching prefix', () => { + const command = 'mget'; + const key = 'test-key,cache:test-key'; + const result = shouldConsiderForCache(command, key, prefixes); + expect(result).toBe(true); + }); + + it('should return false for multiple keys without any matching prefix', () => { + const command = 'mget'; + const key = 'test-key,test-key2'; + const result = shouldConsiderForCache(command, key, prefixes); + expect(result).toBe(false); + }); + + GET_COMMANDS.concat(SET_COMMANDS).forEach(command => { + it(`should return true for ${command} command with matching prefix`, () => { + const key = 'cache:test-key'; + const result = shouldConsiderForCache(command, key, prefixes); + expect(result).toBe(true); + }); + }); + }); +}); diff --git a/packages/opentelemetry/src/utils/parseSpanDescription.ts b/packages/opentelemetry/src/utils/parseSpanDescription.ts index 9888e8b5b0dd..2955dc84ca39 100644 --- a/packages/opentelemetry/src/utils/parseSpanDescription.ts +++ b/packages/opentelemetry/src/utils/parseSpanDescription.ts @@ -14,6 +14,7 @@ import { import type { TransactionSource } from '@sentry/types'; import { getSanitizedUrlString, parseUrl, stripUrlQueryAndFragment } from '@sentry/utils'; +import { SEMANTIC_ATTRIBUTE_SENTRY_OP } from '@sentry/core'; import type { AbstractSpan } from '../types'; import { getSpanKind } from './getSpanKind'; import { spanHasAttributes, spanHasName } from './spanTypes'; @@ -43,9 +44,13 @@ export function parseSpanDescription(span: AbstractSpan): SpanDescription { return descriptionForHttpMethod({ attributes, name, kind: getSpanKind(span) }, httpMethod); } - // If db.type exists then this is a database call span. + // If db.type exists then this is a database call span (unless OP is cache) const dbSystem = attributes[SEMATTRS_DB_SYSTEM]; - if (dbSystem) { + if ( + dbSystem && + typeof attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] === 'string' && + !attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP].startsWith('cache.') + ) { return descriptionForDbSystem({ attributes, name }); } From 320855bb9f14943de3c8036ce3f05940e5d3450e Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Tue, 4 Jun 2024 14:21:29 +0200 Subject: [PATCH 27/54] fix tests --- packages/opentelemetry/src/utils/parseSpanDescription.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry/src/utils/parseSpanDescription.ts b/packages/opentelemetry/src/utils/parseSpanDescription.ts index 2955dc84ca39..80bef2c4865a 100644 --- a/packages/opentelemetry/src/utils/parseSpanDescription.ts +++ b/packages/opentelemetry/src/utils/parseSpanDescription.ts @@ -48,8 +48,9 @@ export function parseSpanDescription(span: AbstractSpan): SpanDescription { const dbSystem = attributes[SEMATTRS_DB_SYSTEM]; if ( dbSystem && - typeof attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] === 'string' && - !attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP].startsWith('cache.') + (!attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] || + (typeof attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] === 'string' && + !attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP].startsWith('cache.'))) ) { return descriptionForDbSystem({ attributes, name }); } From 3ded60f9edb6d536600cbee009503179ce772963 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Tue, 4 Jun 2024 14:58:14 +0200 Subject: [PATCH 28/54] review comments --- .../suites/tracing/redis-cache/test.ts | 4 +-- .../node/src/integrations/tracing/redis.ts | 14 +++++++--- packages/node/src/utils/redisCache.ts | 23 +++++++--------- .../test/integrations/tracing/redis.test.ts | 27 ++++++++++++------- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts index 740c85e6d6e0..8e1db732cfcc 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts @@ -91,14 +91,14 @@ describe('redis cache auto instrumentation', () => { }), // MGET expect.objectContaining({ - description: 'test-key,ioredis-cache:test-key,ioredis-cache:unavailable-data', + description: 'test-key, ioredis-cache:test-key, ioredis-cache:unavailable-data', op: 'cache.get', origin: 'auto.db.otel.redis', data: expect.objectContaining({ 'sentry.origin': 'auto.db.otel.redis', 'db.statement': 'mget [3 other arguments]', 'cache.hit': true, - 'cache.key': 'test-key,ioredis-cache:test-key,ioredis-cache:unavailable-data', + 'cache.key': 'test-key, ioredis-cache:test-key, ioredis-cache:unavailable-data', 'network.peer.address': 'localhost', 'network.peer.port': 6379, }), diff --git a/packages/node/src/integrations/tracing/redis.ts b/packages/node/src/integrations/tracing/redis.ts index 0a3924e98412..cf4b27a677aa 100644 --- a/packages/node/src/integrations/tracing/redis.ts +++ b/packages/node/src/integrations/tracing/redis.ts @@ -50,20 +50,26 @@ export const instrumentRedis = generateInstrumentOnce(INTEGRATION_NAME, () => { const cacheOperation = getCacheOperation(redisCommand); - if (!cacheOperation) return; // redis command unsupported as cache operation + if (!cacheOperation) { + // redis command unsupported as cache operation + return; + } const cacheItemSize = calculateCacheItemSize(response); - if (cacheItemSize) span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_ITEM_SIZE, cacheItemSize); + if (cacheItemSize) { + span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_ITEM_SIZE, cacheItemSize); + } - if (cacheOperation === 'cache.get' && cacheItemSize !== undefined) + if (cacheOperation === 'cache.get' && cacheItemSize !== undefined) { span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_HIT, cacheItemSize > 0); + } span.setAttributes({ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: cacheOperation, [SEMANTIC_ATTRIBUTE_CACHE_KEY]: safeKey, }); - span.updateName(`${safeKey.substring(0, 1024)}...`); + span.updateName(safeKey.length > 1024 ? `${safeKey.substring(0, 1024)}...` : safeKey); }, }); }); diff --git a/packages/node/src/utils/redisCache.ts b/packages/node/src/utils/redisCache.ts index bbb7b302f4a8..a3a547204872 100644 --- a/packages/node/src/utils/redisCache.ts +++ b/packages/node/src/utils/redisCache.ts @@ -32,23 +32,20 @@ export function getCacheKeySafely(cmdArgs: IORedisCommandArgs): string { const keys: string[] = []; - cmdArgs.forEach(arg => { - if (typeof arg === 'string' || typeof arg === 'number') { - keys.push(arg.toString()); - } else if (Buffer.isBuffer(arg)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const processArg = (arg: string | Buffer | number | any[]): void => { + if (typeof arg === 'string' || typeof arg === 'number' || Buffer.isBuffer(arg)) { keys.push(arg.toString()); } else if (Array.isArray(arg)) { - arg.forEach(subArg => { - if (typeof subArg === 'string' || typeof subArg === 'number') { - keys.push(subArg.toString()); - } else if (Buffer.isBuffer(subArg)) { - keys.push(subArg.toString()); - } - }); + arg.forEach(processArg); + } else { + keys.push(''); } - }); + }; + + cmdArgs.forEach(processArg); - return keys.join(','); + return keys.join(', '); } catch (e) { return ''; } diff --git a/packages/node/test/integrations/tracing/redis.test.ts b/packages/node/test/integrations/tracing/redis.test.ts index ab226f9e7358..03c69e78771d 100644 --- a/packages/node/test/integrations/tracing/redis.test.ts +++ b/packages/node/test/integrations/tracing/redis.test.ts @@ -22,19 +22,19 @@ describe('Redis', () => { it('should return a comma-separated string for multiple arguments', () => { const cmdArgs = ['key1', 'key2', 'key3']; const result = getCacheKeySafely(cmdArgs); - expect(result).toBe('key1,key2,key3'); + expect(result).toBe('key1, key2, key3'); }); it('should handle number arguments', () => { const cmdArgs = [1, 2, 3]; const result = getCacheKeySafely(cmdArgs); - expect(result).toBe('1,2,3'); + expect(result).toBe('1, 2, 3'); }); it('should handle Buffer arguments', () => { const cmdArgs = [Buffer.from('key1'), Buffer.from('key2')]; const result = getCacheKeySafely(cmdArgs); - expect(result).toBe('key1,key2'); + expect(result).toBe('key1, key2'); }); it('should handle array arguments', () => { @@ -43,21 +43,30 @@ describe('Redis', () => { ['key3', 'key4'], ]; const result = getCacheKeySafely(cmdArgs); - expect(result).toBe('key1,key2,key3,key4'); + expect(result).toBe('key1, key2, key3, key4'); }); it('should handle mixed type arguments', () => { - const cmdArgs = [Buffer.from('key1'), ['key2', 'key3'], [Buffer.from('key4'), 'key5', 'key6', 7]]; + const cmdArgs = [Buffer.from('key1'), ['key2', 'key3'], [Buffer.from('key4'), 'key5', 'key6', 7, ['key8']]]; const result = getCacheKeySafely(cmdArgs); - expect(result).toBe('key1,key2,key3,key4,key5,key6,7'); + expect(result).toBe('key1, key2, key3, key4, key5, key6, 7, key8'); }); - it('should return an empty string if an error occurs', () => { - const cmdArgs = [Symbol('key1')]; // Symbols cannot be converted to a string + it('should handle nested arrays in arguments', () => { + const cmdArgs = [ + ['key1', 'key2'], + ['key3', 'key4', ['key5', ['key6']]], + ]; + const result = getCacheKeySafely(cmdArgs); + expect(result).toBe('key1, key2, key3, key4, key5, key6'); + }); + + it('should return if the arg type is not supported', () => { + const cmdArgs = [Symbol('key1')]; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const result = getCacheKeySafely(cmdArgs); - expect(result).toBe(''); + expect(result).toBe(''); }); }); From 99197902c1f79a6e5cde72c85f5e1a1e80c87e2b Mon Sep 17 00:00:00 2001 From: Jonas Date: Tue, 4 Jun 2024 09:09:53 -0400 Subject: [PATCH 29/54] ref(profiling): unref timer (#12340) Tentative fix for https://github.com/getsentry/sentry-javascript/issues/12169 I did not generate a core dump, but by looking at the crash, it seemed like it the segfault was happening after the bindings were required and the code after the require statement had already ran, which hints at an issue with the measurement collection loop. This is a small change, but it ensures that the reference to the timer is not maintained and can be properly collected, as well as adds a missing nullptr check. --- .../profiling-node/bindings/cpu_profiler.cc | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/profiling-node/bindings/cpu_profiler.cc b/packages/profiling-node/bindings/cpu_profiler.cc index 64a82ba61910..6f9651bcd6a4 100644 --- a/packages/profiling-node/bindings/cpu_profiler.cc +++ b/packages/profiling-node/bindings/cpu_profiler.cc @@ -76,7 +76,8 @@ class MeasurementsTicker { MeasurementsTicker(uv_loop_t *loop) : period_ms(100), isolate(v8::Isolate::GetCurrent()) { uv_timer_init(loop, &timer); - timer.data = this; + uv_handle_set_data(reinterpret_cast(&timer), this); + uv_unref(reinterpret_cast(&timer)); } static void ticker(uv_timer_t *); @@ -196,6 +197,10 @@ void MeasurementsTicker::cpu_callback() { }; void MeasurementsTicker::ticker(uv_timer_t *handle) { + if (handle == nullptr) { + return; + } + MeasurementsTicker *self = static_cast(handle->data); self->heap_callback(); self->cpu_callback(); @@ -323,18 +328,6 @@ void SentryProfile::Start(Profiler *profiler) { status = ProfileStatus::kStarted; } -static void CleanupSentryProfile(Profiler *profiler, - SentryProfile *sentry_profile, - const std::string &profile_id) { - if (sentry_profile == nullptr) { - return; - } - - sentry_profile->Stop(profiler); - profiler->active_profiles.erase(profile_id); - delete sentry_profile; -}; - v8::CpuProfile *SentryProfile::Stop(Profiler *profiler) { // Stop the CPU Profiler v8::CpuProfile *profile = profiler->cpu_profiler->StopProfiling( @@ -376,6 +369,18 @@ const uint16_t &SentryProfile::cpu_usage_write_index() const { return cpu_write_index; }; +static void CleanupSentryProfile(Profiler *profiler, + SentryProfile *sentry_profile, + const std::string &profile_id) { + if (sentry_profile == nullptr) { + return; + } + + sentry_profile->Stop(profiler); + profiler->active_profiles.erase(profile_id); + delete sentry_profile; +}; + #ifdef _WIN32 static const char kPlatformSeparator = '\\'; static const char kWinDiskPrefix = ':'; @@ -1049,6 +1054,7 @@ void FreeAddonData(napi_env env, void *data, void *hint) { if (profiler->cpu_profiler != nullptr) { profiler->cpu_profiler->Dispose(); + profiler->cpu_profiler = nullptr; } delete profiler; From e4618cb7ee5df48b95c9433b4b2145af91e87bfa Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Tue, 4 Jun 2024 17:43:17 +0200 Subject: [PATCH 30/54] review comments --- .../suites/tracing/redis-cache/test.ts | 2 +- packages/node/src/utils/redisCache.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts index 8e1db732cfcc..1d3151286294 100644 --- a/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/redis-cache/test.ts @@ -53,7 +53,7 @@ describe('redis cache auto instrumentation', () => { origin: 'auto.db.otel.redis', data: expect.objectContaining({ 'sentry.origin': 'auto.db.otel.redis', - 'db.statement': 'ioredis-cache:test-key [1 other arguments]', + 'db.statement': 'set ioredis-cache:test-key [1 other arguments]', 'cache.key': 'ioredis-cache:test-key', 'cache.item_size': 2, 'network.peer.address': 'localhost', diff --git a/packages/node/src/utils/redisCache.ts b/packages/node/src/utils/redisCache.ts index a3a547204872..b30bd1ead2c5 100644 --- a/packages/node/src/utils/redisCache.ts +++ b/packages/node/src/utils/redisCache.ts @@ -54,8 +54,9 @@ export function getCacheKeySafely(cmdArgs: IORedisCommandArgs): string { /** Determines whether a redis operation should be considered as "cache operation" by checking if a key is prefixed. * We only support certain commands (such as 'set', 'get', 'mget'). */ export function shouldConsiderForCache(redisCommand: string, key: string, prefixes: string[]): boolean { - const lowercaseCommand = redisCommand.toLowerCase(); - if (!SET_COMMANDS.includes(lowercaseCommand) && !GET_COMMANDS.includes(lowercaseCommand)) return false; + if (!getCacheOperation(redisCommand)) { + return false; + } return key.split(',').reduce((prev, key) => prev || keyHasPrefix(key, prefixes), false); } From 7de5ea40e29125b0092138e419e0a3be20c296d5 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 5 Jun 2024 03:06:34 -0400 Subject: [PATCH 31/54] chore: Extract v7 changelog into separate doc (#12354) resolves https://github.com/getsentry/sentry-javascript/issues/12353 Also adds entries for `7.113.0` to `7.116.0` --- CHANGELOG.md | 3739 +---------------------------------------- docs/changelog/v7.md | 3791 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 3793 insertions(+), 3737 deletions(-) create mode 100644 docs/changelog/v7.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a0a90df7089..2183a51fc166 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1372,3744 +1372,9 @@ We have also removed or updated a variety of deprecated APIs. - ref: Remove usage of span tags (#10808) - ref: Remove user segment (#10575) -## 7.112.2 +## 7.x -- fix(nextjs|sveltekit): Ensure we can pass `browserTracingIntegration` (#11765) - -## 7.112.1 - -- fix(ember/v7): Do not create rendering spans without transaction (#11750) - -## 7.112.0 - -### Important Changes - -- **feat: Export pluggable integrations from SDK packages (#11723)** - -Instead of installing `@sentry/integrations`, you can now import the pluggable integrations directly from your SDK -package: - -```js -// Before -import * as Sentry fromv '@sentry/browser'; -import { dedupeIntegration } from '@sentry/integrations'; - -Sentry.init({ - integrations: [dedupeIntegration()], -}); - -// After -import * as Sentry from '@sentry/browser'; - -Sentry.init({ - integrations: [Sentry.dedupeIntegration()], -}); -``` - -Note that only the functional integrations (e.g. `xxxIntegration()`) are re-exported. - -### Other Changes - -- feat(replay): Add "maxCanvasSize" option for replay canvases (#11732) -- fix(serverless): [v7] Check if cloud event callback is a function (#11734) - -## 7.111.0 - -- feat(core): Add `server.address` to browser `http.client` spans (#11663) -- fix: Ensure next & sveltekit correctly handle `browserTracingIntegration` (#11647) -- fix(browser): Don't assume window.document is available (#11598) - -## 7.110.1 - -- fix(nextjs): Fix `tunnelRoute` matching logic for hybrid cloud (#11577) - -## 7.110.0 - -### Important Changes - -- **feat(tracing): Add interactions sample rate to browser tracing integrations (#11382)** - -You can now use a `interactionsSampleRate` to control the sample rate of INP spans. `interactionsSampleRate` is applied -on top of the global `tracesSampleRate`. Therefore if `interactionsSampleRate` is `0.5` and `tracesSampleRate` is `0.1`, -then the actual sample rate for interactions is `0.05`. - -```js -Sentry.init({ - tracesSampleRate: 0.1, - integrations: [ - Sentry.browserTracingIntegration({ - interactionsSampleRate: 0.5, - }), - ], -}); -``` - -- **Deprecations** - -This release deprecates the `Hub` class, as well as the `addRequestDataToTransaction` method. The `trpcMiddleware` -method is no longer on the `Handlers` export, but instead is a standalone export. - -Please see the detailed [Migration docs](./MIGRATION.md#deprecations-in-7x) on how to migrate to the new APIs. - -- feat: Deprecate and relocate `trpcMiddleware` (#11389) -- feat(core): Deprecate `Hub` class (#11528) -- feat(types): Deprecate `Hub` interface (#11530) -- ref: Deprecate `addRequestDataToTransaction` (#11368) - -### Other Changes - -- feat(core): Update metric normalization (#11519) -- feat(feedback): Customize feedback placeholder text color (#11521) -- feat(remix): Skip span creation for `OPTIONS` and `HEAD` request. (#11485) -- feat(utils): Add metric buckets rate limit (#11506) -- fix(core): unref timer to not block node exit (#11483) -- fix(metrics): Map `statsd` to `metric_bucket` (#11505) -- fix(spans): Allow zero exclusive time for INP spans (#11408) -- ref(feedback): Configure feedback fonts (#11520) - -## 7.109.0 - -This release deprecates some exports from the `@sentry/replay` package. These exports have been moved to the browser SDK -(or related framework SDKs like `@sentry/react`). - -- feat(feedback): Make "required" text for input elements configurable (#11287) -- feat(node): Add scope to ANR events (#11267) -- feat(replay): Bump `rrweb` to 2.12.0 (#11317) -- fix(node): Local variables skipped after Promise (#11248) -- fix(node): Skip capturing Hapi Boom error responses (#11324) -- fix(web-vitals): Check for undefined navigation entry (#11312) -- ref(replay): Deprecate `@sentry/replay` exports (#11242) - -Work in this release contributed by @soerface. Thank you for your contribution! - -## 7.108.0 - -This release fixes issues with Time to First Byte (TTFB) calculation in the SDK that was introduced with `7.95.0`. It -also fixes some bugs with Interaction to First Paint (INP) instrumentation. This may impact your Sentry Performance -Score calculation. - -- feat(serverless): Add Node.js 20 to compatible runtimes (#11104) -- feat(core): Backport `ResizeObserver` and `googletag` default filters (#11210) -- feat(webvitals): Adds event entry names for INP handler. Also guard against empty metric value -- fix(metrics): use correct statsd data category (#11187) -- fix(node): Record local variables with falsy values (v7) (#11190) -- fix(node): Use unique variable for ANR context transfer (v7) (#11162) -- fix(node): Time zone handling for `cron` (#11225) -- fix(tracing): use web-vitals ttfb calculation (#11231) -- fix(types): Fix incorrect `sampled` type on `Transaction` (#11146) -- fix(webvitals): Fix mapping not being maintained properly and sometimes not sending INP spans (#11183) - -Work in this release contributed by @quisido and @joshkel. Thank you for your contributions! - -## 7.107.0 - -This release fixes issues with INP instrumentation with the Next.js SDK and adds support for the `enableInp` option in -the deprecated `BrowserTracing` integration for backwards compatibility. - -- feat(performance): Port INP span instrumentation to old browser tracing (#11085) -- fix(ember): Ensure browser tracing is correctly lazy loaded (#11027) -- fix(node): Do not assert in vendored proxy code (v7 backport) (#11009) -- fix(react): Set `handled` value in ErrorBoundary depending on fallback [v7] (#11037) - -## 7.106.1 - -- fix(nextjs/v7): Use passthrough `createReduxEnhancer` on server (#11010) - -## 7.106.0 - -- feat(nextjs): Support Hybrid Cloud DSNs with `tunnelRoute` option (#10958) -- feat(remix): Add Vite dev-mode support to Express instrumentation (#10811) -- fix(core): Undeprecate `setTransactionName` -- fix(browser): Don't use chrome variable name (#10874) -- fix(nextjs): Client code should not use Node `global` (#10925) -- fix(node): support undici headers as strings or arrays (#10938) -- fix(types): Add `AttachmentType` and use for envelope `attachment_type` property (#10946) -- ref(ember): Avoid namespace import to hopefully resolve minification issue (#10885) -- chore(sveltekit): Fix punctuation in a console.log (#10895) - -Work in this release contributed by @jessezhang91 and @bfontaine. Thank you for your contributions! - -## 7.105.0 - -### Important Changes - -- **feat: Ensure `withActiveSpan` is exported everywhere (#10877)** - -You can use the `withActiveSpan` method to ensure a certain span is the active span in a given callback. This can be -used to create a span as a child of a specific span with the `startSpan` API methods: - -```js -const parentSpan = Sentry.startInactiveSpan({ name: 'parent' }); -if (parentSpan) { - withActiveSpan(parentSpan, () => { - // This will be a direct child of parentSpan - const childSpan = Sentry.startInactiveSpan({ name: 'child' }); - }); -} -``` - -## 7.104.0 - -### Important Changes - -- **feat(performance): create Interaction standalone spans on inp events (#10709)** - -This release adds support for the INP web vital. This is currently only supported for Saas Sentry, and product support -is released with the upcoming `24.3.0` release of self-hosted. - -To opt-in to this feature, you can use the `enableInp` option in the `browserTracingIntegration`: - -```js -Sentry.init({ - integrations: [ - Sentry.browserTracingIntegration({ - enableInp: true, - }); - ] -}) -``` - -### Other Changes - -- feat(feedback): Flush replays when feedback form opens (#10567) -- feat(profiling-node): Expose `nodeProfilingIntegration` (#10864) -- fix(profiling-node): Fix dependencies to point to current versions (#10861) -- fix(replay): Add `errorHandler` for replayCanvas integration (#10796) - -## 7.103.0 - -### Important Changes - -- **feat(core): Allow to pass `forceTransaction` to `startSpan()` APIs (#10819)** - -You can now pass `forceTransaction: true` to `startSpan()`, `startSpanManual()` and `startInactiveSpan()`. This allows -you to start a span that you want to be a transaction, if possible. Under the hood, the SDK will connect this span to -the running active span (if there is one), but still send the new span as a transaction to the Sentry backend, if -possible, ensuring it shows up as a transaction throughout the system. - -Please note that setting this to `true` does not _guarantee_ that this will be sent as a transaction, but that the SDK -will try to do so. You can enable this flag if this span is important to you and you want to ensure that you can see it -in the Sentry UI. - -### Other Changes - -- fix: Make breadcrumbs option optional in WinterCGFetch integration (#10792) - -## 7.102.1 - -- fix(performance): Fixes latest route name and source for interactions not updating properly on navigation (#10702) -- fix(tracing): Guard against missing `window.location` (#10659) -- ref: Make span types more robust (#10660) -- ref(remix): Make `@remix-run/router` a dependency (v7) (#10779) - -## 7.102.0 - -- fix: Export session API (#10712) -- fix(core): Fix scope capturing via `captureContext` function (#10737) - -## 7.101.1 - -In version 7.101.0 the `@sentry/hub` package was missing due to a publishing issue. This release contains the package -again. - -- fix(nextjs): Remove `webpack://` prefix more broadly from source map `sources` field (#10641) - -## 7.101.0 - -- feat: Export semantic attribute keys from SDK packages (#10637) -- feat(core): Add metric summaries to spans (#10554) -- feat(core): Deprecate the `Hub` constructor (#10584) -- feat(core): Make custom tracing methods return spans & set default op (#10633) -- feat(replay): Add `getReplay` utility function (#10510) -- fix(angular-ivy): Add `exports` field to `package.json` (#10569) -- fix(sveltekit): Avoid capturing Http 4xx errors on the client (#10571) -- fix(sveltekit): Properly await sourcemaps flattening (#10602) - -## 7.100.1 - -This release contains build fixes for profiling-node. - -- build(profiling-node): make sure debug build plugin is used #10534 -- build: Only run profiling e2e test if bindings have changed #10542 -- fix(feedback): Replay breadcrumb for feedback events was incorrect #10536 - -## 7.100.0 - -### Important Changes - -#### Deprecations - -This release includes some deprecations. For more details please look at our -[migration guide](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md). - -The deprecation most likely to affect you is the one of `BrowserTracing`. Instead of `new BrowserTracing()`, you should -now use `browserTracingIntegration()`, which will also handle framework-specific instrumentation out of the box for -you - no need to pass a custom `routingInstrumentation` anymore. For `@sentry/react`, we expose dedicated integrations -for the different react-router versions: - -- `reactRouterV6BrowserTracingIntegration()` -- `reactRouterV5BrowserTracingIntegration()` -- `reactRouterV4BrowserTracingIntegration()` -- `reactRouterV3BrowserTracingIntegration()` - -See the -[migration guide](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md#depreacted-browsertracing-integration) -for details. - -- feat(angular): Export custom `browserTracingIntegration()` (#10353) -- feat(browser): Deprecate `BrowserTracing` integration (#10493) -- feat(browser): Export `browserProfilingIntegration` (#10438) -- feat(bun): Export `bunServerIntegration()` (#10439) -- feat(nextjs): Add `browserTracingIntegration` (#10397) -- feat(react): Add `reactRouterV3BrowserTracingIntegration` for react router v3 (#10489) -- feat(react): Add `reactRouterV4/V5BrowserTracingIntegration` for react router v4 & v5 (#10488) -- feat(react): Add `reactRouterV6BrowserTracingIntegration` for react router v6 & v6.4 (#10491) -- feat(remix): Add custom `browserTracingIntegration` (#10442) -- feat(node): Expose functional integrations to replace classes (#10356) -- feat(vercel-edge): Replace `WinterCGFetch` with `winterCGFetchIntegration` (#10436) -- feat: Deprecate non-callback based `continueTrace` (#10301) -- feat(vue): Deprecate `new VueIntegration()` (#10440) -- feat(vue): Implement vue `browserTracingIntegration()` (#10477) -- feat(sveltekit): Add custom `browserTracingIntegration()` (#10450) - -#### Profiling Node - -`@sentry/profiling-node` has been ported into the monorepo. Future development for it will happen here! - -- pkg(profiling-node): port profiling-node repo to monorepo (#10151) - -### Other Changes - -- feat: Export `setHttpStatus` from all packages (#10475) -- feat(bundles): Add pluggable integrations on CDN to `Sentry` namespace (#10452) -- feat(core): Pass `name` & `attributes` to `tracesSampler` (#10426) -- feat(feedback): Add `system-ui` to start of font family (#10464) -- feat(node-experimental): Add koa integration (#10451) -- feat(node-experimental): Update opentelemetry packages (#10456) -- feat(node-experimental): Update tracing integrations to functional style (#10443) -- feat(replay): Bump `rrweb` to 2.10.0 (#10445) -- feat(replay): Enforce masking of credit card fields (#10472) -- feat(utils): Add `propagationContextFromHeaders` (#10313) -- fix: Make `startSpan`, `startSpanManual` and `startInactiveSpan` pick up the scopes at time of creation instead of - termination (#10492) -- fix(feedback): Fix logo color when colorScheme is "system" (#10465) -- fix(nextjs): Do not report redirects and notFound calls as errors in server actions (#10474) -- fix(nextjs): Fix navigation tracing on app router (#10502) -- fix(nextjs): Apply server action data to correct isolation scope (#10514) -- fix(node): Use normal `require` call to import Undici (#10388) -- ref(nextjs): Remove internally used deprecated APIs (#10453) -- ref(vue): use startInactiveSpan in tracing mixin (#10406) - -## 7.99.0 - -### Important Changes - -#### Deprecations - -This release includes some deprecations for span related methods and integrations in our Deno SDK, `@sentry/deno`. For -more details please look at our -[migration guide](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md). - -- feat(core): Deprecate `Span.setHttpStatus` in favor of `setHttpStatus` (#10268) -- feat(core): Deprecate `spanStatusfromHttpCode` in favour of `getSpanStatusFromHttpCode` (#10361) -- feat(core): Deprecate `StartSpanOptions.origin` in favour of passing attribute (#10274) -- feat(deno): Expose functional integrations to replace classes (#10355) - -### Other Changes - -- feat(bun): Add missing `@sentry/node` re-exports (#10396) -- feat(core): Add `afterAllSetup` hook for integrations (#10345) -- feat(core): Ensure `startSpan()` can handle spans that require parent (#10386) -- feat(core): Read propagation context off scopes in `startSpan` APIs (#10300) -- feat(remix): Export missing `@sentry/node` functions (#10385, #10391) -- feat(serverless): Add missing `@sentry/node` re-exports (#10390) -- feat(sveltekit): Add more missing `@sentry/node` re-exports (#10392) -- feat(tracing): Export proper type for browser tracing (#10411) -- feat(tracing): Expose new `browserTracingIntegration` (#10351) -- fix: Ensure `afterAllSetup` is called when using `addIntegration()` (#10372) -- fix(core): Export `spanToTraceContext` function from span utils (#10364) -- fix(core): Make `FunctionToString` integration use SETUP_CLIENTS weakmap (#10358) -- fix(deno): Call function if client is not setup (#10354) -- fix(react): Fix attachReduxState option (#10381) -- fix(spotlight): Use unpatched http.request (#10369) -- fix(tracing): Only create request span if there is active span (#10375) -- ref: Read propagation context off of scope and isolation scope when propagating and applying trace context (#10297) - -Work in this release contributed by @AleshaOleg. Thank you for your contribution! - -## 7.98.0 - -This release primarily fixes some type declaration errors: - -- feat(core): Export `IntegrationIndex` type (#10337) -- fix(nextjs): Fix Http integration type declaration (#10338) -- fix(node): Fix type definitions (#10339) - -## 7.97.0 - -Note: The 7.96.0 release was incomplete. This release is partially encompassing changes from `7.96.0`. - -- feat(react): Add `stripBasename` option for React Router 6 (#10314) - -## 7.96.0 - -Note: This release was incomplete. Not all Sentry SDK packages were released for this version. Please upgrade to 7.98.0 -directly. - -### Important Changes - -#### Deprecations - -This release includes some deprecations for integrations in `@sentry/browser` and frontend framework SDKs -(`@sentry/react`, `@sentry/vue`, etc.). Please take a look at our -[migration guide](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md) for more details. - -- feat(browser): Export functional integrations & deprecate classes (#10267) - -#### Web Vitals Fix for LCP and CLS - -This release fixes an issue with the Web Vitals integration where LCP and CLS were not being captured correctly, -increasing capture rate by 10-30% for some apps. LCP and CLS capturing issues were introduced with version `7.75.0`. - -- fix(tracing): Ensure web vitals are correctly stopped/captured (#10323) - -### Other Changes - -- fix(node): Fix `node-cron` types and add test (#10315) -- fix(node): Fix downleveled types entry point (#10321) -- fix(node): LocalVariables integration should use setupOnce (#10307) -- fix(replay): Fix type for options of replayIntegration (#10325) - -Work in this release contributed by @Shubhdeep12. Thank you for your contribution! - -## 7.95.0 - -### Important Changes - -#### Deprecations - -This release includes some deprecations in preparation for v8. - -Most notably, it deprecates the `Replay` & `Feedback` classes in favor of a functional replacement: - -```js -import * as Sentry from '@sentry/browser'; - -Sentry.init({ - integrations: [ - // Instead of - new Sentry.Replay(), - new Sentry.Feedback(), - // Use the functional replacement: - Sentry.replayIntegration(), - Sentry.feedbackIntegration(), - ], -}); -``` - -- feat(core): Deprecate `Span.origin` in favor of `sentry.origin` attribute (#10260) -- feat(core): Deprecate `Span.parentSpanId` (#10244) -- feat(core): Expose `isInitialized()` to replace checking via `getClient` (#10296) -- feat(replay): Deprecate `Replay`, `ReplayCanvas`, `Feedback` classes (#10270) -- feat(wasm): Deprecate `Wasm` integration class (#10230) - -### Other Changes - -- feat: Make `parameterize` function available through browser and node API (#10085) -- feat(feedback): Configure feedback border radius (#10289) -- feat(sveltekit): Update default integration handling & deprecate `addOrUpdateIntegration` (#10263) -- fix(replay-canvas): Add missing dependency on @sentry/utils (#10279) -- fix(tracing): Don't send negative ttfb (#10286) - -Work in this release contributed by @AleshaOleg. Thank you for your contribution! - -## 7.94.1 - -This release fixes a publishing issue. - -## 7.94.0 - -### Important Changes - -#### Deprecations - -As we're moving closer to the next major version of the SDK, more public APIs were deprecated. - -To get a head start on migrating to the replacement APIs, please take a look at our -[migration guide](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md). - -- feat: Deprecate user segment field (#10210) -- feat(core): Deprecate `finish` on `Span` interface in favour of `end` (#10161) -- feat(core): Deprecate `getCurrentHub()` (#10200) -- feat(core): Deprecate `hub.bindClient()` & `makeMain()` (#10188) -- feat(core): Deprecate `Span.instrumenter` (#10139) -- feat(core): Deprecate `Span.isSuccess()` in favor of reading span status (#10213) -- feat(core): Deprecate `Span.op` in favor of op attribute (#10189) -- feat(core): Deprecate `Span.spanRecorder` (#10199) -- feat(core): Deprecate `Span.status` (#10208) -- feat(core): Deprecate `Span.transaction` in favor of `getRootSpan` (#10134) -- feat(core): Deprecate `Transaction.instrumenter` (#10162) -- feat(core): Deprecate `Transaction.setMeasurement` in favor of `setMeasurement` (#10182) -- feat(core): Deprecate integration classes & `Integrations.X` (#10198) -- feat(core): Deprecate methods on `Hub` (#10124) -- feat(core): Deprecate remaining `setName` declarations on `Transaction` and `Span` (#10164) -- feat(core): Deprecate span `startTimestamp` & `endTimestamp` (#10192) -- feat(core): Deprecate `hub.bindClient()` and `makeMain()` (#10118) -- feat(types): Deprecate `op` on `Span` interface (#10217) -- feat(integrations): Deprecate `Transaction` integration (#10178) -- feat(integrations): Deprecate pluggable integration classes (#10211) - -#### Replay & Canvas - -We have added a new `ReplayCanvas` integration (#10112), which you can add to capture the contents of canvas elements -with Replay. - -Just add it _in addition_ to the regular replay integration: - -```js -Sentry.init({ - integrations: [new Sentry.Replay(), new Sentry.ReplayCanvas()], -}); -``` - -### Other Changes - -- feat(core): Add `client.getIntegrationByName()` (#10130) -- feat(core): Add `client.init()` to replace `client.setupIntegrations()` (#10118) -- feat(core): Add `withActiveSpan` (#10195) -- feat(core): Add `withIsolationScope` (#10141) -- feat(core): Streamline integration function results to be compatible (#10135) -- feat(core): Write data from `setUser`, `setTags`, `setExtras`, `setTag`, `setExtra`, and `setContext` to isolation - scope (#10163) -- feat(core): Add domain information to resource span data #10205 -- feat(feedback): Export sendFeedback from @sentry/browser (#10231) -- feat(node): Update and vendor https-proxy-agent (#10088) -- feat(node-experimental): Add `withActiveSpan` (#10194) -- feat(replays): Add snapshot function to replay canvas integration (#10066) -- feat(types): Add `SerializedEvent` interface (pre v8) (#10240) -- feat(types): Add support for new monitor config thresholds (#10225) -- fix: Ensure all integration classes have correct types (#10183) -- fix(astro): Fix import path when using external init files with default path (#10214) -- fix(cdn): Emit console warning instead of error for integration shims (#10193) -- fix(core): Take user from current scope when starting a session (#10153) -- fix(node-experimental): Ensure `http.status_code` is always a string (#10177) -- fix(node): Guard against `process.argv[1]` being undefined (#10155) -- fix(node): Module name resolution (#10144) -- fix(node): Remove leading slash in Windows filenames (#10147) -- fix(remix): Capture thrown fetch responses. (#10166) -- fix(tracing): Gate mongo operation span data behind sendDefaultPii (#10227) -- fix(tracing-internal): Delay pageload transaction finish until document is interactive (#10215) -- fix(tracing-internal): Only collect request/response spans when browser performance timing is available (#10207) -- fix(tracing-internal): Prefer `fetch` init headers over `fetch` input headers (#10176) -- fix(utils): Ensure dropUndefinedKeys() does not break class instances (#10245) - -## 7.93.0 - -### Important Changes - -#### Deprecations - -As we're moving closer to the next major version of the SDK, more public APIs were deprecated. - -To get a head start on migrating to the replacement APIs, please take a look at our -[migration guide](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md). - -- feat(core): Deprecate `getActiveTransaction()` & `scope.getTransaction()` (#10098) -- feat(core): Deprecate `Hub.shouldSendDefaultPii` (#10062) -- feat(core): Deprecate `new Transaction()` (#10125) -- feat(core): Deprecate `scope.getSpan()` & `scope.setSpan()` (#10114) -- feat(core): Deprecate `scope.setTransactionName()` (#10113) -- feat(core): Deprecate `span.startChild()` (#10091) -- feat(core): Deprecate `startTransaction()` (#10073) -- feat(core): Deprecate `Transaction.getDynamicSamplingContext` in favor of `getDynamicSamplingContextFromSpan` (#10094) -- feat(core): Deprecate arguments for `startSpan()` (#10101) -- feat(core): Deprecate hub capture APIs and add them to `Scope` (#10039) -- feat(core): Deprecate session APIs on hub and add global replacements (#10054) -- feat(core): Deprecate span `name` and `description` (#10056) -- feat(core): Deprecate span `tags`, `data`, `context` & setters (#10053) -- feat(core): Deprecate transaction metadata in favor of attributes (#10097) -- feat(core): Deprecate `span.sampled` in favor of `span.isRecording()` (#10034) -- ref(node-experimental): Deprecate `lastEventId` on scope (#10093) - -#### Cron Monitoring Support for `node-schedule` library - -This release adds auto instrumented check-ins for the `node-schedule` library. - -```ts -import * as Sentry from '@sentry/node'; -import * as schedule from 'node-schedule'; - -const scheduleWithCheckIn = Sentry.cron.instrumentNodeSchedule(schedule); - -const job = scheduleWithCheckIn.scheduleJob('my-cron-job', '* * * * *', () => { - console.log('You will see this message every minute'); -}); -``` - -- feat(node): Instrumentation for `node-schedule` library (#10086) - -### Other Changes - -- feat(core): Add `span.spanContext()` (#10037) -- feat(core): Add `spanToJSON()` method to get span properties (#10074) -- feat(core): Allow to pass `scope` to `startSpan` APIs (#10076) -- feat(core): Allow to pass start/end timestamp for spans flexibly (#10060) -- feat(node): Make `getModuleFromFilename` compatible with ESM (#10061) -- feat(replay): Update rrweb to 2.7.3 (#10072) -- feat(utils): Add `parameterize` function (#9145) -- fix(astro): Use correct package name for CF (#10099) -- fix(core): Do not run `setup` for integration on client multiple times (#10116) -- fix(core): Ensure we copy passed in span data/tags/attributes (#10105) -- fix(cron): Make name required for instrumentNodeCron option (#10070) -- fix(nextjs): Don't capture not-found and redirect errors in generation functions (#10057) -- fix(node): `LocalVariables` integration should have correct name (#10084) -- fix(node): Anr events should have an `event_id` (#10068) -- fix(node): Revert to only use sync debugger for `LocalVariables` (#10077) -- fix(node): Update ANR min node version to v16.17.0 (#10107) - -## 7.92.0 - -### Important Changes - -#### Deprecations - -- feat(core): Add `span.updateName()` and deprecate `span.setName()` (#10018) -- feat(core): Deprecate `span.getTraceContext()` (#10032) -- feat(core): Deprecate `span.toTraceparent()` in favor of `spanToTraceHeader()` util (#10031) -- feat(core): Deprecate `trace` in favor of `startSpan` (#10012) -- feat(core): Deprecate span `toContext()` and `updateWithContext()` (#10030) -- ref: Deprecate `deepReadDirSync` (#10016) -- ref: Deprecate `lastEventId()` (#10043) - -Please take a look at the [Migration docs](./MIGRATION.md) for more details. These methods will be removed in the -upcoming [v8 major release](https://github.com/getsentry/sentry-javascript/discussions/9802). - -#### Cron Monitoring Support for `cron` and `node-cron` libraries - -- feat(node): Instrumentation for `cron` library (#9999) -- feat(node): Instrumentation for `node-cron` library (#9904) - -This release adds instrumentation for the `cron` and `node-cron` libraries. This allows you to monitor your cron jobs -with [Sentry cron monitors](https://docs.sentry.io/product/crons/). - -For [`cron`](https://www.npmjs.com/package/cron): - -```js -import * as Sentry from '@sentry/node'; -import { CronJob } from 'cron'; - -const CronJobWithCheckIn = Sentry.cron.instrumentCron(CronJob, 'my-cron-job'); - -// use the constructor -const job = new CronJobWithCheckIn('* * * * *', () => { - console.log('You will see this message every minute'); -}); - -// or from -const job = CronJobWithCheckIn.from({ - cronTime: '* * * * *', - onTick: () => { - console.log('You will see this message every minute'); - }, -}); -``` - -For [`node-cron`](https://www.npmjs.com/package/node-cron): - -```js -import * as Sentry from '@sentry/node'; -import cron from 'node-cron'; - -const cronWithCheckIn = Sentry.cron.instrumentNodeCron(cron); - -cronWithCheckIn.schedule( - '* * * * *', - () => { - console.log('running a task every minute'); - }, - { name: 'my-cron-job' }, -); -``` - -### Other Changes - -- feat(astro): Add `enabled` option to Astro integration options (#10007) -- feat(core): Add `attributes` to `Span` (#10008) -- feat(core): Add `setClient()` and `getClient()` to `Scope` (#10055) -- feat(integrations): Capture error cause with `captureErrorCause` in `ExtraErrorData` integration (#9914) -- feat(node-experimental): Allow to pass base span options to trace methods (#10006) -- feat(node): Local variables via async inspector in node 19+ (#9962) -- fix(astro): handle commonjs related issues (#10042) -- fix(astro): Handle non-utf8 encoded streams in middleware (#9989) -- fix(astro): prevent sentry from externalized (#9994) -- fix(core): Ensure `withScope` sets current scope correctly with async callbacks (#9974) -- fix(node): ANR fixes and additions (#9998) -- fix(node): Anr should not block exit (#10035) -- fix(node): Correctly resolve module name (#10001) -- fix(node): Handle inspector already open (#10025) -- fix(node): Make `NODE_VERSION` properties required (#9964) -- fix(node): Anr doesn't block exit (#10064) -- fix(utils): use correct typeof URL validation (#10028) -- perf(astro): reduce unnecessary path resolutions (#10021) -- ref(astro): Use astro logger instead of console (#9995) -- ref(remix): Isolate Express instrumentation from server auto-instrumentation. (#9966) - -Work in this release contributed by @joshkel. Thank you for your contribution! - -## 7.91.0 - -### Important Changes - -- **feat: Add server runtime metrics aggregator (#9894)** - -The release adds alpha support for [Sentry developer metrics](https://github.com/getsentry/sentry/discussions/58584) in -the server runtime SDKs (`@sentry/node`, `@sentry/deno`, `@sentry/nextjs` server-side, etc.). Via the newly introduced -APIs, you can now flush metrics directly to Sentry. - -To enable capturing metrics, you first need to add the `metricsAggregator` experiment to your `Sentry.init` call. - -```js -Sentry.init({ - dsn: '__DSN__', - _experiments: { - metricsAggregator: true, - }, -}); -``` - -Then you'll be able to add `counters`, `sets`, `distributions`, and `gauges` under the `Sentry.metrics` namespace. - -```js -// Add 4 to a counter named `hits` -Sentry.metrics.increment('hits', 4); - -// Add 2 to gauge named `parallel_requests`, tagged with `type: "a"` -Sentry.metrics.gauge('parallel_requests', 2, { tags: { type: 'a' } }); - -// Add 4.6 to a distribution named `response_time` with unit seconds -Sentry.metrics.distribution('response_time', 4.6, { unit: 'seconds' }); - -// Add 2 to a set named `valuable.ids` -Sentry.metrics.set('valuable.ids', 2); -``` - -- **feat(node): Rework ANR to use worker script via an integration (#9945)** - -The [ANR tracking integration for Node](https://docs.sentry.io/platforms/node/configuration/application-not-responding/) -has been reworked to use an integration. ANR tracking now requires a minimum Node version of 16 or higher. Previously -you had to call `Sentry.enableANRDetection` before running your application, now you can simply add the `Anr` -integration to your `Sentry.init` call. - -```js -import * as Sentry from '@sentry/node'; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })], -}); -``` - -### Other Changes - -- feat(breadcrumbs): Send component names on UI breadcrumbs (#9946) -- feat(core): Add `getGlobalScope()` method (#9920) -- feat(core): Add `getIsolationScope()` method (#9957) -- feat(core): Add `span.end()` to replace `span.finish()` (#9954) -- feat(core): Ensure `startSpan` & `startSpanManual` fork scope (#9955) -- feat(react): Send component name on spans (#9949) -- feat(replay): Send component names in replay breadcrumbs (#9947) -- feat(sveltekit): Add options to configure fetch instrumentation script for CSP (#9969) -- feat(tracing): Send component name on interaction spans (#9948) -- feat(utils): Add function to extract relevant component name (#9921) -- fix(core): Rethrow caught promise rejections in `startSpan`, `startSpanManual`, `trace` (#9958) - -## 7.90.0 - -- feat(replay): Change to use preset quality values (#9903) -- fix(replay): Adjust development hydration error messages (#9922) -- fix(sveltekit): Add `types` field to package.json `exports` (#9926) - -## 7.89.0 - -### Important Changes - -#### Deprecations - -- **feat(core): Deprecate `configureScope` (#9887)** -- **feat(core): Deprecate `pushScope` & `popScope` (#9890)** - -This release deprecates `configureScope`, `pushScope`, and `popScope`, which will be removed in the upcoming v8 major -release. - -#### Hapi Integration - -- **feat(node): Add Hapi Integration (#9539)** - -This release adds an integration for Hapi. It can be used as follows: - -```ts -const Sentry = require('@sentry/node'); -const Hapi = require('@hapi/hapi'); - -const init = async () => { - const server = Hapi.server({ - // your server configuration ... - }); - - Sentry.init({ - dsn: '__DSN__', - tracesSampleRate: 1.0, - integrations: [new Sentry.Integrations.Hapi({ server })], - }); - - server.route({ - // your route configuration ... - }); - - await server.start(); -}; -``` - -#### SvelteKit 2.0 - -- **chore(sveltekit): Add SvelteKit 2.0 to peer dependencies (#9861)** - -This release adds support for SvelteKit 2.0 in the `@sentry/sveltekit` package. If you're upgrading from SvelteKit 1.x -to 2.x and already use the Sentry SvelteKit SDK, no changes apart from upgrading to this (or a newer) version are -necessary. - -### Other Changes - -- feat(core): Add type & utility for function-based integrations (#9818) -- feat(core): Update `withScope` to return callback return value (#9866) -- feat(deno): Support `Deno.CronSchedule` for cron jobs (#9880) -- feat(nextjs): Auto instrument generation functions (#9781) -- feat(nextjs): Connect server component transactions if there is no incoming trace (#9845) -- feat(node-experimental): Update to new Scope APIs (#9799) -- feat(replay): Add `canvas.type` setting (#9877) -- fix(nextjs): Export `createReduxEnhancer` (#9854) -- fix(remix): Do not capture thrown redirect responses. (#9909) -- fix(sveltekit): Add conditional exports (#9872) -- fix(sveltekit): Avoid capturing 404 errors on client side (#9902) -- fix(utils): Do not use `Event` type in worldwide (#9864) -- fix(utils): Support crypto.getRandomValues in old Chromium versions (#9251) -- fix(utils): Update `eventFromUnknownInput` to avoid scope pollution & `getCurrentHub` (#9868) -- ref: Use `addBreadcrumb` directly & allow to pass hint (#9867) - -Work in this release contributed by @adam187, and @jghinestrosa. Thank you for your contributions! - -## 7.88.0 - -### Important Changes - -- **feat(browser): Add browser metrics sdk (#9794)** - -The release adds alpha support for [Sentry developer metrics](https://github.com/getsentry/sentry/discussions/58584) in -the Browser SDKs (`@sentry/browser` and related framework SDKs). Via the newly introduced APIs, you can now flush -metrics directly to Sentry. - -To enable capturing metrics, you first need to add the `MetricsAggregator` integration. - -```js -Sentry.init({ - dsn: '__DSN__', - integrations: [new Sentry.metrics.MetricsAggregator()], -}); -``` - -Then you'll be able to add `counters`, `sets`, `distributions`, and `gauges` under the `Sentry.metrics` namespace. - -```js -// Add 4 to a counter named `hits` -Sentry.metrics.increment('hits', 4); - -// Add 2 to gauge named `parallel_requests`, tagged with `happy: "no"` -Sentry.metrics.gauge('parallel_requests', 2, { tags: { happy: 'no' } }); - -// Add 4.6 to a distribution named `response_time` with unit seconds -Sentry.metrics.distribution('response_time', 4.6, { unit: 'seconds' }); - -// Add 2 to a set named `valuable.ids` -Sentry.metrics.set('valuable.ids', 2); -``` - -In a future release we'll add support for server runtimes (Node, Deno, Bun, Vercel Edge, etc.) - -- **feat(deno): Optionally instrument `Deno.cron` (#9808)** - -This releases add support for instrumenting [Deno cron's](https://deno.com/blog/cron) with -[Sentry cron monitors](https://docs.sentry.io/product/crons/). This requires v1.38 of Deno run with the `--unstable` -flag and the usage of the `DenoCron` Sentry integration. - -```ts -// Import from the Deno registry -import * as Sentry from 'https://deno.land/x/sentry/index.mjs'; - -Sentry.init({ - dsn: '__DSN__', - integrations: [new Sentry.DenoCron()], -}); -``` - -### Other Changes - -- feat(replay): Bump `rrweb` to 2.6.0 (#9847) -- fix(nextjs): Guard against injecting multiple times (#9807) -- ref(remix): Bump Sentry CLI to ^2.23.0 (#9773) - -## 7.87.0 - -- feat: Add top level `getCurrentScope()` method (#9800) -- feat(replay): Bump `rrweb` to 2.5.0 (#9803) -- feat(replay): Capture hydration error breadcrumb (#9759) -- feat(types): Add profile envelope types (#9798) -- fix(astro): Avoid RegExp creation during route interpolation (#9815) -- fix(browser): Avoid importing from `./exports` (#9775) -- fix(nextjs): Catch rejecting flushes (#9811) -- fix(nextjs): Fix devserver CORS blockage when `assetPrefix` is defined (#9766) -- fix(node): Capture errors in tRPC middleware (#9782) - -## 7.86.0 - -- feat(core): Use SDK_VERSION for hub API version (#9732) -- feat(nextjs): Emit warning if your app directory doesn't have a global-error.js file (#9753) -- feat(node): Add cloudflare pages commit sha (#9751) -- feat(remix): Bump @sentry/cli to 2.22.3 (#9741) -- fix(nextjs): Don't accidentally trigger static generation bailout (#9749) -- fix(node): Guard `process.env.NODE_ENV` access in Spotlight integration (#9748) -- fix(utils): Fix XHR instrumentation early return (#9770) -- ref(remix): Rework Error Handling (#9725) - -## 7.85.0 - -- feat(core): Add `addEventProcessor` method (#9554) -- feat(crons): Add interface for heartbeat checkin (#9706) -- feat(feedback): Include Feedback package in browser SDK (#9586) -- fix(astro): Isolate request instrumentation in middleware (#9709) -- fix(replay): Capture JSON XHR response bodies (#9623) -- ref(feedback): Change form `box-shadow` to use CSS var (#9630) - -## 7.84.0 - -### Important Changes - -- **ref(nextjs): Set `automaticVercelMonitors` to be `false` by default (#9697)** - -From this version onwards the default for the `automaticVercelMonitors` option in the Next.js SDK is set to false. -Previously, if you made use of Vercel Crons the SDK automatically instrumented the relevant routes to create Sentry -monitors. Because this feature will soon be generally available, we are now flipping the default to avoid situations -where quota is used unexpectedly. - -If you want to continue using this feature, make sure to set the `automaticVercelMonitors` flag to `true` in your -`next.config.js` Sentry settings. - -### Other Changes - -- chore(astro): Add 4.0.0 preview versions to `astro` peer dependency range (#9696) -- feat(metrics): Add interfaces for metrics (#9698) -- feat(web-vitals): Vendor in INP from web-vitals library (#9690) -- fix(astro): Avoid adding the Sentry Vite plugin in dev mode (#9688) -- fix(nextjs): Don't match files called `middleware` in node_modules (#9686) -- fix(remix): Don't capture error responses that are not 5xx on Remix v2. (#9655) -- fix(tracing): Don't attach resource size if null (#9669) -- fix(utils): Regex match port to stop accidental replace (#9676) -- fix(utils): Try catch new URL when extracting query params (#9675) - -## 7.83.0 - -- chore(astro): Allow Astro 4.0 in peer dependencies (#9683) -- feat(astro): Add `assets` option to source maps upload options (#9668) -- feat(react): Support `exactOptionalPropertyTypes` on `ErrorBoundary` (#9098) -- fix: Don't depend on browser types in `types` (#9682) -- fix(astro): Configure sourcemap assets directory for Vercel adapter (#9665) -- fix(remix): Check the error data before spreading. (#9664) - -## 7.82.0 - -- feat(astro): Automatically add Sentry middleware in Astro integration (#9532) -- feat(core): Add optional `setup` hook to integrations (#9556) -- feat(core): Add top level `getClient()` method (#9638) -- feat(core): Allow to pass `mechanism` as event hint (#9590) -- feat(core): Allow to use `continueTrace` without callback (#9615) -- feat(feedback): Add onClose callback to showReportDialog (#9433) (#9550) -- feat(nextjs): Add request data to all edge-capable functionalities (#9636) -- feat(node): Add Spotlight option to Node SDK (#9629) -- feat(utils): Refactor `addInstrumentationHandler` to dedicated methods (#9542) -- fix: Make full url customizable for Spotlight (#9652) -- fix(astro): Remove Auth Token existence check (#9651) -- fix(nextjs): Fix middleware detection logic (#9637) -- fix(remix): Skip capturing aborted requests (#9659) -- fix(replay): Add `BODY_PARSE_ERROR` warning & time out fetch response load (#9622) -- fix(tracing): Filter out invalid resource sizes (#9641) -- ref: Hoist `RequestData` integration to `@sentry/core` (#9597) -- ref(feedback): Rename onDialog* to onForm*, remove onActorClick (#9625) - -Work in this release contributed by @arya-s. Thank you for your contribution! - -## 7.81.1 - -- fix(astro): Remove method from span op (#9603) -- fix(deno): Make sure files get published (#9611) -- fix(nextjs): Use `globalThis` instead of `global` in edge runtime (#9612) -- fix(node): Improve error handling and shutdown handling for ANR (#9548) -- fix(tracing-internal): Fix case when originalURL contain query params (#9531) - -Work in this release contributed by @powerfulyang, @LubomirIgonda1, @joshkel, and @alexgleason. Thank you for your -contributions! - -## 7.81.0 - -### Important Changes - -**- feat(nextjs): Add instrumentation utility for server actions (#9553)** - -This release adds a utility function `withServerActionInstrumentation` to the `@sentry/nextjs` SDK for instrumenting -your Next.js server actions with error and performance monitoring. - -You can optionally pass form data and headers to record them, and configure the wrapper to record the Server Action -responses: - -```tsx -import * as Sentry from '@sentry/nextjs'; -import { headers } from 'next/headers'; - -export default function ServerComponent() { - async function myServerAction(formData: FormData) { - 'use server'; - return await Sentry.withServerActionInstrumentation( - 'myServerAction', // The name you want to associate this Server Action with in Sentry - { - formData, // Optionally pass in the form data - headers: headers(), // Optionally pass in headers - recordResponse: true, // Optionally record the server action response - }, - async () => { - // ... Your Server Action code - - return { name: 'John Doe' }; - }, - ); - } - - return ( -
- - -
- ); -} -``` - -### Other Changes - -- docs(feedback): Example docs on `sendFeedback` (#9560) -- feat(feedback): Add `level` and remove breadcrumbs from feedback event (#9533) -- feat(vercel-edge): Add fetch instrumentation (#9504) -- feat(vue): Support Vue 3 lifecycle hooks in mixin options (#9578) -- fix(nextjs): Download CLI binary if it can't be found (#9584) -- ref: Deprecate `extractTraceParentData` from `@sentry/core` & downstream packages (#9158) -- ref(replay): Add further logging to network body parsing (#9566) - -Work in this release contributed by @snoozbuster. Thank you for your contribution! - -## 7.80.1 - -- fix(astro): Adjust Vite plugin config to upload server source maps (#9541) -- fix(nextjs): Add tracing extensions in all serverside wrappers (#9537) -- fix(nextjs): Fix serverside transaction names on Windows (#9526) -- fix(node): Fix tRPC middleware typing (#9540) -- fix(replay): Add additional safeguards for capturing network bodies (#9506) -- fix(tracing): Update prisma span to be `db.prisma` (#9512) - -## 7.80.0 - -- feat(astro): Add distributed tracing via `` tags (#9483) -- feat(node): Capture internal server errors in trpc middleware (#9482) -- feat(remix): Export a type to use for `MetaFunction` parameters (#9493) -- fix(astro): Mark SDK package as Astro-external (#9509) -- ref(nextjs): Don't initialize Server SDK during build (#9503) - -## 7.79.0 - -- feat(tracing): Add span `origin` to trace context (#9472) -- fix(deno): Emit .mjs files (#9485) -- fix(nextjs): Flush servercomponent events for edge (#9487) - -## 7.78.0 - -### Important Changes - -- **Replay Bundle Size improvements** - -We've dramatically decreased the bundle size of our Replay package, reducing the minified & gzipped bundle size by ~20 -KB! This was possible by extensive use of tree shaking and a host of small changes to reduce our footprint: - -- feat(replay): Update rrweb to 2.2.0 (#9414) -- ref(replay): Use fflate instead of pako for compression (#9436) - -By using [tree shaking](https://docs.sentry.io/platforms/javascript/configuration/tree-shaking/) it is possible to shave -up to 10 additional KB off the bundle. - -### Other Changes - -- feat(astro): Add Sentry middleware (#9445) -- feat(feedback): Add "outline focus" and "foreground hover" vars (#9462) -- feat(feedback): Add `openDialog` and `closeDialog` onto integration interface (#9464) -- feat(feedback): Implement new user feedback embeddable widget (#9217) -- feat(nextjs): Add automatic sourcemapping for edge part of the SDK (#9454) -- feat(nextjs): Add client routing instrumentation for app router (#9446) -- feat(node-experimental): Add hapi tracing support (#9449) -- feat(replay): Allow to configure `beforeErrorSampling` (#9470) -- feat(replay): Stop fixing truncated JSONs in SDK (#9437) -- fix(nextjs): Fix sourcemaps resolving for local dev when basePath is set (#9457) -- fix(nextjs): Only inject basepath in dev mode (#9465) -- fix(replay): Ensure we stop for rate limit headers (#9420) -- ref(feedback): Add treeshaking for logger statements (#9475) -- ref(replay): Use rrweb for slow click detection (#9408) -- build(polyfills): Remove output format specific logic (#9467) - -## 7.77.0 - -### Security Fixes - -- fix(nextjs): Match only numbers as orgid in tunnelRoute (#9416) (CVE-2023-46729) -- fix(nextjs): Strictly validate tunnel target parameters (#9415) (CVE-2023-46729) - -### Other Changes - -- feat: Move LinkedErrors integration to @sentry/core (#9404) -- feat(remix): Update sentry-cli version to ^2.21.2 (#9401) -- feat(replay): Allow to treeshake & configure compression worker URL (#9409) -- fix(angular-ivy): Adjust package entry points to support Angular 17 with SSR config (#9412) -- fix(feedback): Fixing feedback import (#9403) -- fix(utils): Avoid keeping a reference of last used event (#9387) - -## 7.76.0 - -### Important Changes - -- **feat(core): Add cron monitor wrapper helper (#9395)** - -This release adds `Sentry.withMonitor()`, a wrapping function that wraps a callback with a cron monitor that will -automatically report completions and failures: - -```ts -import * as Sentry from '@sentry/node'; - -// withMonitor() will send checkin when callback is started/finished -// works with async and sync callbacks. -const result = Sentry.withMonitor( - 'dailyEmail', - () => { - // withCheckIn return value is same return value here - return sendEmail(); - }, - // Optional upsert options - { - schedule: { - type: 'crontab', - value: '0 * * * *', - }, - // 🇨🇦🫡 - timezone: 'Canada/Eastern', - }, -); -``` - -### Other Changes - -- chore(angular-ivy): Allow Angular 17 in peer dependencies (#9386) -- feat(nextjs): Instrument SSR page components (#9346) -- feat(nextjs): Trace errors in page component SSR (#9388) -- fix(nextjs): Instrument route handlers with `jsx` and `tsx` file extensions (#9362) -- fix(nextjs): Trace with performance disabled (#9389) -- fix(replay): Ensure `replay_id` is not added to DSC if session expired (#9359) -- fix(replay): Remove unused parts of pako from build (#9369) -- fix(serverless): Don't mark all errors as unhandled (#9368) -- fix(tracing-internal): Fix case when middleware contain array of routes with special chars as @ (#9375) -- meta(nextjs): Bump peer deps for Next.js 14 (#9390) - -Work in this release contributed by @LubomirIgonda1. Thank you for your contribution! - -## 7.75.1 - -- feat(browser): Allow collecting of pageload profiles (#9317) -- fix(browser): Correct timestamp on pageload profiles (#9350) -- fix(nextjs): Use webpack plugin release value to inject release (#9348) - -## 7.75.0 - -### Important Changes - -- **feat(opentelemetry): Add new `@sentry/opentelemetry` package (#9238)** - -This release publishes a new package, `@sentry/opentelemetry`. This is a runtime agnostic replacement for -`@sentry/opentelemetry-node` and exports a couple of useful utilities which can be used to use Sentry together with -OpenTelemetry. - -You can read more about -[@sentry/opentelemetry in the Readme](https://github.com/getsentry/sentry-javascript/tree/develop/packages/opentelemetry). - -- **feat(replay): Allow to treeshake rrweb features (#9274)** - -Starting with this release, you can configure the following build-time flags in order to reduce the SDK bundle size: - -- `__RRWEB_EXCLUDE_CANVAS__` -- `__RRWEB_EXCLUDE_IFRAME__` -- `__RRWEB_EXCLUDE_SHADOW_DOM__` - -You can read more about -[tree shaking in our docs](https://docs.sentry.io/platforms/javascript/configuration/tree-shaking/). - -### Other Changes - -- build(deno): Prepare Deno SDK for release on npm (#9281) -- feat: Remove tslib (#9299) -- feat(node): Add abnormal session support for ANR (#9268) -- feat(node): Remove `lru_map` dependency (#9300) -- feat(node): Vendor `cookie` module (#9308) -- feat(replay): Share performance instrumentation with tracing (#9296) -- feat(types): Add missing Profiling types (macho debug image, profile measurements, stack frame properties) (#9277) -- feat(types): Add statsd envelope types (#9304) -- fix(astro): Add integration default export to types entry point (#9337) -- fix(astro): Convert SDK init file import paths to POSIX paths (#9336) -- fix(astro): Make `Replay` and `BrowserTracing` integrations tree-shakeable (#9287) -- fix(integrations): Fix transaction integration (#9334) -- fix(nextjs): Restore `autoInstrumentMiddleware` functionality (#9323) -- fix(nextjs): Guard for case where `getInitialProps` may return undefined (#9342) -- fix(node-experimental): Make node-fetch support optional (#9321) -- fix(node): Check buffer length when attempting to parse ANR frame (#9314) -- fix(replay): Fix xhr start timestamps (#9341) -- fix(tracing-internal): Remove query params from urls with a trailing slash (#9328) -- fix(types): Remove typo with CheckInEnvelope (#9303) - -## 7.74.1 - -- chore(astro): Add `astro-integration` keyword (#9265) -- fix(core): Narrow filters for health check transactions (#9257) -- fix(nextjs): Fix HMR by inserting new entrypoints at the end (#9267) -- fix(nextjs): Fix resolution of request async storage module (#9259) -- fix(node-experimental): Guard against missing `fetch` (#9275) -- fix(remix): Update `defer` injection logic. (#9242) -- fix(tracing-internal): Parameterize express middleware parameters (#8668) -- fix(utils): Move Node specific ANR impl. out of utils (#9258) - -Work in this release contributed by @LubomirIgonda1. Thank you for your contribution! - -## 7.74.0 - -### Important Changes - -- **feat(astro): Add `sentryAstro` integration (#9218)** - -This Release introduces the first alpha version of our new SDK for Astro. At this time, the SDK is considered -experimental and things might break and change in future versions. - -The core of the SDK is an Astro integration which you easily add to your Astro config: - -```js -// astro.config.js -import { defineConfig } from 'astro/config'; -import sentry from '@sentry/astro'; - -export default defineConfig({ - integrations: [ - sentry({ - dsn: '__DSN__', - sourceMapsUploadOptions: { - project: 'astro', - authToken: process.env.SENTRY_AUTH_TOKEN, - }, - }), - ], -}); -``` - -Check out the [README](./packages/astro/README.md) for usage instructions and what to expect from this alpha release. - -### Other Changes - -- feat(core): Add `addIntegration` utility (#9186) -- feat(core): Add `continueTrace` method (#9164) -- feat(node-experimental): Add NodeFetch integration (#9226) -- feat(node-experimental): Use native OTEL Spans (#9161, #9214) -- feat(node-experimental): Sample in OTEL Sampler (#9203) -- feat(serverlesss): Allow disabling transaction traces (#9154) -- feat(tracing): Allow direct pg module to enable esbuild support (#9227) -- feat(utils): Move common node ANR code to utils (#9191) -- feat(vue): Expose `VueIntegration` to initialize vue app later (#9180) -- fix: Don't set `referrerPolicy` on serverside fetch transports (#9200) -- fix: Ensure we never mutate options passed to `init` (#9162) -- fix(ember): Avoid pulling in utils at build time (#9221) -- fix(ember): Drop undefined config values (#9175) -- fix(node): Ensure mysql integration works without callback (#9222) -- fix(node): Only require `inspector` when needed (#9149) -- fix(node): Remove ANR `debug` option and instead add logger.isEnabled() (#9230) -- fix(node): Strip `.mjs` and `.cjs` extensions from module name (#9231) -- fix(replay): bump rrweb to 2.0.1 (#9240) -- fix(replay): Fix potential broken CSS in styled-components (#9234) -- fix(sveltekit): Flush in server wrappers before exiting (#9153) -- fix(types): Update signature of `processEvent` integration hook (#9151) -- fix(utils): Dereference DOM events after they have servered their purpose (#9224) -- ref(integrations): Refactor pluggable integrations to use `processEvent` (#9021) -- ref(serverless): Properly deprecate `rethrowAfterCapture` option (#9159) -- ref(utils): Deprecate `walk` method (#9157) - -Work in this release contributed by @aldenquimby. Thank you for your contributions! - -## 7.73.0 - -### Important Changes - -- **feat(replay): Upgrade to rrweb2** - -This is fully backwards compatible with prior versions of the Replay SDK. The only breaking change that we will making -is to not be masking `aria-label` by default. The reason for this change is to align with our core SDK which also does -not mask `aria-label`. This change also enables better support of searching by clicks. - -Another change that needs to be highlighted is the 13% bundle size increase. This bundle size increase is necessary to -bring improved recording performance and improved replay fidelity, especially in regards to web components and iframes. -We will be investigating the reduction of the bundle size in -[this PR](https://github.com/getsentry/sentry-javascript/issues/8815). - -Here are benchmarks comparing the version 1 of rrweb to version 2 - -| metric | v1 | v2 | -| --------- | ---------- | ---------- | -| lcp | 1486.06 ms | 1529.11 ms | -| cls | 0.40 ms | 0.40 ms | -| fid | 1.53 ms | 1.50 ms | -| tbt | 3207.22 ms | 3036.80 ms | -| memoryAvg | 131.83 MB | 124.84 MB | -| memoryMax | 324.8 MB | 339.03 MB | -| netTx | 282.67 KB | 272.51 KB | -| netRx | 8.02 MB | 8.07 MB | - -### Other Changes - -- feat: Always assemble Envelopes (#9101) -- feat(node): Rate limit local variables for caught exceptions and enable `captureAllExceptions` by default (#9102) -- fix(core): Ensure `tunnel` is considered for `isSentryUrl` checks (#9130) -- fix(nextjs): Fix `RequestAsyncStorage` fallback path (#9126) -- fix(node-otel): Suppress tracing for generated sentry spans (#9142) -- fix(node): fill in span data from http request options object (#9112) -- fix(node): Fixes and improvements to ANR detection (#9128) -- fix(sveltekit): Avoid data invalidation in wrapped client-side `load` functions (#9071) -- ref(core): Refactor `InboundFilters` integration to use `processEvent` (#9020) -- ref(wasm): Refactor Wasm integration to use `processEvent` (#9019) - -Work in this release contributed by @vlad-zhukov. Thank you for your contribution! - -## 7.72.0 - -### Important Changes - -- **feat(node): App Not Responding with stack traces (#9079)** - -This release introduces support for Application Not Responding (ANR) errors for Node.js applications. These errors are -triggered when the Node.js main thread event loop of an application is blocked for more than five seconds. The Node SDK -reports ANR errors as Sentry events and can optionally attach a stacktrace of the blocking code to the ANR event. - -To enable ANR detection, import and use the `enableANRDetection` function from the `@sentry/node` package before you run -the rest of your application code. Any event loop blocking before calling `enableANRDetection` will not be detected by -the SDK. - -Example (ESM): - -```ts -import * as Sentry from '@sentry/node'; - -Sentry.init({ - dsn: '___PUBLIC_DSN___', - tracesSampleRate: 1.0, -}); - -await Sentry.enableANRDetection({ captureStackTrace: true }); -// Function that runs your app -runApp(); -``` - -Example (CJS): - -```ts -const Sentry = require('@sentry/node'); - -Sentry.init({ - dsn: '___PUBLIC_DSN___', - tracesSampleRate: 1.0, -}); - -Sentry.enableANRDetection({ captureStackTrace: true }).then(() => { - // Function that runs your app - runApp(); -}); -``` - -### Other Changes - -- fix(nextjs): Filter `RequestAsyncStorage` locations by locations that webpack will resolve (#9114) -- fix(replay): Ensure `replay_id` is not captured when session is expired (#9109) - -## 7.71.0 - -- feat(bun): Instrument Bun.serve (#9080) -- fix(core): Ensure global event processors are always applied to event (#9064) -- fix(core): Run client eventProcessors before global ones (#9032) -- fix(nextjs): Use webpack module paths to attempt to resolve internal request async storage module (#9100) -- fix(react): Add actual error name to boundary error name (#9065) -- fix(react): Compare location against `basename`-prefixed route. (#9076) -- ref(browser): Refactor browser integrations to use `processEvent` (#9022) - -Work in this release contributed by @jorrit. Thank you for your contribution! - -## 7.70.0 - -### Important Changes - -- **feat: Add Bun SDK (#9029)** - -This release contains the beta version of `@sentry/bun`, our SDK for the [Bun JavaScript runtime](https://bun.sh/)! For -details on how to use it, please see the [README](./packages/bun/README.md). Any feedback/bug reports are greatly -appreciated, please [reach out on GitHub](https://github.com/getsentry/sentry-javascript/discussions/7979). - -Note that as of now the Bun runtime does not support global error handlers. This is being actively worked on, see -[the tracking issue in Bun's GitHub repo](https://github.com/oven-sh/bun/issues/5091). - -- **feat(remix): Add Remix 2.x release support. (#8940)** - -The Sentry Remix SDK now officially supports Remix v2! See -[our Remix docs for more details](https://docs.sentry.io/platforms/javascript/guides/remix/). - -### Other Changes - -- chore(node): Upgrade cookie to ^0.5.0 (#9013) -- feat(core): Introduce `processEvent` hook on `Integration` (#9017) -- feat(node): Improve non-error messages (#9026) -- feat(vercel-edge): Add Vercel Edge Runtime package (#9041) -- fix(remix): Use `React.ComponentType` instead of `React.FC` as `withSentry`'s generic type. (#9043) -- fix(replay): Ensure replay events go through `preprocessEvent` hook (#9034) -- fix(replay): Fix typo in Replay types (#9028) -- fix(sveltekit): Adjust `handleErrorWithSentry` type (#9054) -- fix(utils): Try-catch monkeypatching to handle frozen objects/functions (#9031) - -Work in this release contributed by @Dima-Dim, @krist7599555 and @lifeiscontent. Thank you for your contributions! - -Special thanks for @isaacharrisholt for helping us implement a Vercel Edge Runtime SDK which we use under the hood for -our Next.js SDK. - -## 7.69.0 - -### Important Changes - -- **New Performance APIs** - - feat: Update span performance API names (#8971) - - feat(core): Introduce startSpanManual (#8913) - -This release introduces a new set of top level APIs for the Performance Monitoring SDKs. These aim to simplify creating -spans and reduce the boilerplate needed for performance instrumentation. The three new methods introduced are -`Sentry.startSpan`, `Sentry.startInactiveSpan`, and `Sentry.startSpanManual`. These methods are available in the browser -and node SDKs. - -`Sentry.startSpan` wraps a callback in a span. The span is automatically finished when the callback returns. This is the -recommended way to create spans. - -```js -// Start a span that tracks the duration of expensiveFunction -const result = Sentry.startSpan({ name: 'important function' }, () => { - return expensiveFunction(); -}); - -// You can also mutate the span wrapping the callback to set data or status -Sentry.startSpan({ name: 'important function' }, span => { - // span is undefined if performance monitoring is turned off or if - // the span was not sampled. This is done to reduce overhead. - span?.setData('version', '1.0.0'); - return expensiveFunction(); -}); -``` - -If you don't want the span to finish when the callback returns, use `Sentry.startSpanManual` to control when the span is -finished. This is useful for event emitters or similar. - -```js -// Start a span that tracks the duration of middleware -function middleware(_req, res, next) { - return Sentry.startSpanManual({ name: 'middleware' }, (span, finish) => { - res.once('finish', () => { - setHttpStatus(span, res.status); - finish(); - }); - return next(); - }); -} -``` - -`Sentry.startSpan` and `Sentry.startSpanManual` create a span and make it active for the duration of the callback. Any -spans created while this active span is running will be added as a child span to it. If you want to create a span -without making it active, use `Sentry.startInactiveSpan`. This is useful for creating parallel spans that are not -related to each other. - -```js -const span1 = Sentry.startInactiveSpan({ name: 'span1' }); - -someWork(); - -const span2 = Sentry.startInactiveSpan({ name: 'span2' }); - -moreWork(); - -const span3 = Sentry.startInactiveSpan({ name: 'span3' }); - -evenMoreWork(); - -span1?.finish(); -span2?.finish(); -span3?.finish(); -``` - -### Other Changes - -- feat(core): Export `BeforeFinishCallback` type (#8999) -- build(eslint): Enforce that ts-expect-error is used (#8987) -- feat(integration): Ensure `LinkedErrors` integration runs before all event processors (#8956) -- feat(node-experimental): Keep breadcrumbs on transaction (#8967) -- feat(redux): Add 'attachReduxState' option (#8953) -- feat(remix): Accept `org`, `project` and `url` as args to upload script (#8985) -- fix(utils): Prevent iterating over VueViewModel (#8981) -- fix(utils): uuidv4 fix for cloudflare (#8968) -- fix(core): Always use event message and exception values for `ignoreErrors` (#8986) -- fix(nextjs): Add new potential location for Next.js request AsyncLocalStorage (#9006) -- fix(node-experimental): Ensure we only create HTTP spans when outgoing (#8966) -- fix(node-experimental): Ignore OPTIONS & HEAD requests (#9001) -- fix(node-experimental): Ignore outgoing Sentry requests (#8994) -- fix(node-experimental): Require parent span for `pg` spans (#8993) -- fix(node-experimental): Use Sentry logger as Otel logger (#8960) -- fix(node-otel): Refactor OTEL span reference cleanup (#9000) -- fix(react): Switch to props in `useRoutes` (#8998) -- fix(remix): Add `glob` to Remix SDK dependencies. (#8963) -- fix(replay): Ensure `handleRecordingEmit` aborts when event is not added (#8938) -- fix(replay): Fully stop & restart session when it expires (#8834) - -Work in this release contributed by @Duncanxyz and @malay44. Thank you for your contributions! - -## 7.68.0 - -- feat(browser): Add `BroadcastChannel` and `SharedWorker` to TryCatch EventTargets (#8943) -- feat(core): Add `name` to `Span` (#8949) -- feat(core): Add `ServerRuntimeClient` (#8930) -- fix(node-experimental): Ensure `span.finish()` works as expected (#8947) -- fix(remix): Add new sourcemap-upload script files to prepack assets. (#8948) -- fix(publish): Publish downleveled TS3.8 types and fix types path (#8954) - -## 7.67.0 - -### Important Changes - -- **feat: Mark errors caught by the SDK as unhandled** - - feat(browser): Mark errors caught from `TryCatch` integration as unhandled (#8890) - - feat(integrations): Mark errors caught from `HttpClient` and `CaptureConsole` integrations as unhandled (#8891) - - feat(nextjs): Mark errors caught from NextJS wrappers as unhandled (#8893) - - feat(react): Mark errors captured from ErrorBoundary as unhandled (#8914) - - feat(remix): Add debugid injection and map deletion to sourcemaps script (#8814) - - feat(remix): Mark errors caught from Remix instrumentation as unhandled (#8894) - - feat(serverless): Mark errors caught in Serverless handlers as unhandled (#8907) - - feat(vue): Mark errors caught by Vue wrappers as unhandled (#8905) - -This release fixes inconsistent behaviour of when our SDKs classify captured errors as unhandled. Previously, some of -our instrumentations correctly set unhandled, while others set handled. Going forward, all errors caught automatically -from our SDKs will be marked as unhandled. If you manually capture errors (e.g. by calling `Sentry.captureException`), -your errors will continue to be reported as handled. - -This change might lead to a decrease in reported crash-free sessions and consequently in your release health score. If -you have concerns about this, feel free to open an issue. - -### Other Changes - -- feat(node-experimental): Implement new performance APIs (#8911) -- feat(node-experimental): Sync OTEL context with Sentry AsyncContext (#8797) -- feat(replay): Allow to configure `maxReplayDuration` (#8769) -- fix(browser): Add replay and profiling options to `BrowserClientOptions` (#8921) -- fix(browser): Check for existence of instrumentation targets (#8939) -- fix(nextjs): Don't re-export default in route handlers (#8924) -- fix(node): Improve mysql integration (#8923) -- fix(remix): Guard against missing default export for server instrument (#8909) -- ref(browser): Deprecate top-level `wrap` function (#8927) -- ref(node-otel): Avoid exporting internals & refactor attribute adding (#8920) - -Work in this release contributed by @SorsOps. Thank you for your contribution! - -## 7.66.0 - -- fix: Defer tracing decision to downstream SDKs when using SDK without performance (#8839) -- fix(nextjs): Fix `package.json` exports (#8895) -- fix(sveltekit): Ensure target file exists before applying auto instrumentation (#8881) -- ref: Use consistent console instrumentation (#8879) -- ref(browser): Refactor sentry breadcrumb to use hook (#8892) -- ref(tracing): Add `origin` to spans (#8765) - -## 7.65.0 - -- build: Remove build-specific polyfills (#8809) -- build(deps): bump protobufjs from 6.11.3 to 6.11.4 (#8822) -- deps(sveltekit): Bump `@sentry/vite-plugin` (#8877) -- feat(core): Introduce `Sentry.startActiveSpan` and `Sentry.startSpan` (#8803) -- fix: Memoize `AsyncLocalStorage` instance (#8831) -- fix(nextjs): Check for validity of API route handler signature (#8811) -- fix(nextjs): Fix `requestAsyncStorageShim` path resolution on windows (#8875) -- fix(node): Log entire error object in `OnUncaughtException` (#8876) -- fix(node): More relevant warning message when tracing extensions are missing (#8820) -- fix(replay): Streamline session creation/refresh (#8813) -- fix(sveltekit): Avoid invalidating data on route changes in `wrapServerLoadWithSentry` (#8801) -- fix(tracing): Better guarding for performance observer (#8872) -- ref(sveltekit): Remove custom client fetch instrumentation and use default instrumentation (#8802) -- ref(tracing-internal): Deprecate `tracePropagationTargets` in `BrowserTracing` (#8874) - -## 7.64.0 - -- feat(core): Add setMeasurement export (#8791) -- fix(nextjs): Check for existence of default export when wrapping pages (#8794) -- fix(nextjs): Ensure imports are valid relative paths (#8799) -- fix(nextjs): Only re-export default export if it exists (#8800) - -## 7.63.0 - -- build(deps): bump @opentelemetry/instrumentation from 0.41.0 to 0.41.2 -- feat(eventbuilder): Export `exceptionFromError` for use in hybrid SDKs (#8766) -- feat(node-experimental): Re-export from node (#8786) -- feat(tracing): Add db connection attributes for mysql spans (#8775) -- feat(tracing): Add db connection attributes for postgres spans (#8778) -- feat(tracing): Improve data collection for mongodb spans (#8774) -- fix(nextjs): Execute sentry config independently of `autoInstrumentServerFunctions` and `autoInstrumentAppDirectory` - (#8781) -- fix(replay): Ensure we do not flush if flush took too long (#8784) -- fix(replay): Ensure we do not try to flush when we force stop replay (#8783) -- fix(replay): Fix `hasCheckout` handling (#8782) -- fix(replay): Handle multiple clicks in a short time (#8773) -- ref(replay): Skip events being added too long after initial segment (#8768) - -## 7.62.0 - -### Important Changes - -- **feat(integrations): Add `ContextLines` integration for html-embedded JS stack frames (#8699)** - -This release adds the `ContextLines` integration as an optional integration for the Browser SDKs to -`@sentry/integrations`. - -This integration adds source code from inline JavaScript of the current page's HTML (e.g. JS in `