Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

meta: Add Changelog entry for 8.6.0 #12272

Merged
merged 15 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,7 @@ jobs:
'nextjs-app-dir',
'nextjs-14',
'nextjs-15',
'react-19',
'react-create-hash-router',
'react-router-6-use-routes',
'react-router-5',
Expand Down
54 changes: 54 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,60 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

## 8.6.0

### Important Changes

- **feat(metrics): Add `timings` method to metrics (#12226)**

This introduces a new method, `metrics.timing()`, which can be used in two ways:

1. With a numeric value, to simplify creating a distribution metric. This will default to `second` as unit:

```js
Sentry.metrics.timing('myMetric', 100);
```

2. With a callback, which will wrap the duration of the callback. This can accept a sync or async callback. It will
create an inactive span around the callback and at the end emit a metric with the duration of the span in seconds:

```js
const returnValue = Sentry.metrics.timing('myMetric', measureThisFunction);
```

- **feat(react): Add `Sentry.reactErrorHandler` (#12147)**

This PR introduces `Sentry.reactErrorHandler`, which you can use in React 19 as follows:

```js
import * as Sentry from '@sentry/react';
import { hydrateRoot } from 'react-dom/client';

ReactDOM.hydrateRoot(
document.getElementById('root'),
<React.StrictMode>
<App />
</React.StrictMode>,
{
onUncaughtError: Sentry.reactErrorHandler(),
onCaughtError: Sentry.reactErrorHandler((error, errorInfo) => {
// optional callback if users want custom config.
}),
},
);
```

For more details, take a look at [the PR](https://github.com/getsentry/sentry-javascript/pull/12147). Our
documentation will be updated soon!

### Other Changes

- feat(sveltekit): Add request data to server-side events (#12254)
- fix(core): Pass in cron monitor config correctly (#12248)
- fix(nextjs): Don't capture suspense errors in server components (#12261)
- fix(tracing): Ensure sent spans are limited to 1000 (#12252)
- ref(core): Use versioned carrier on global object (#12206)

## 8.5.0

### Important Changes
Expand Down
140 changes: 137 additions & 3 deletions dev-packages/browser-integration-tests/fixtures/loader.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,139 @@
!function(n,e,r,t,i,o,a,c,s){for(var u=s,f=0;f<document.scripts.length;f++)if(document.scripts[f].src.indexOf(o)>-1){u&&"no"===document.scripts[f].getAttribute("data-lazy")&&(u=!1);break}var p=[];function l(n){return"e"in n}function d(n){return"p"in n}function _(n){return"f"in n}var v=[];function y(n){u&&(l(n)||d(n)||_(n)&&n.f.indexOf("capture")>-1||_(n)&&n.f.indexOf("showReportDialog")>-1)&&m(),v.push(n)}function g(){y({e:[].slice.call(arguments)})}function h(n){y({p:n})}function E(){try{n.SENTRY_SDK_SOURCE="loader";var e=n[i],o=e.init;e.init=function(i){n.removeEventListener(r,g),n.removeEventListener(t,h);var a=c;for(var s in i)Object.prototype.hasOwnProperty.call(i,s)&&(a[s]=i[s]);!function(n,e){var r=n.integrations||[];if(!Array.isArray(r))return;var t=r.map((function(n){return n.name}));n.tracesSampleRate&&-1===t.indexOf("BrowserTracing")&&(e.BrowserTracing?r.push(new e.BrowserTracing):e.browserTracingIntegration&&r.push(e.browserTracingIntegration()));(n.replaysSessionSampleRate||n.replaysOnErrorSampleRate)&&-1===t.indexOf("Replay")&&(e.Replay?r.push(new e.Replay):e.replayIntegration&&r.push(e.replayIntegration()));n.integrations=r}(a,e),o(a)},setTimeout((function(){return function(e){try{"function"==typeof n.sentryOnLoad&&(n.sentryOnLoad(),n.sentryOnLoad=void 0);for(var r=0;r<p.length;r++)"function"==typeof p[r]&&p[r]();p.splice(0);for(r=0;r<v.length;r++){_(o=v[r])&&"init"===o.f&&e.init.apply(e,o.a)}L()||e.init();var t=n.onerror,i=n.onunhandledrejection;for(r=0;r<v.length;r++){var o;if(_(o=v[r])){if("init"===o.f)continue;e[o.f].apply(e,o.a)}else l(o)&&t?t.apply(n,o.e):d(o)&&i&&i.apply(n,[o.p])}}catch(n){console.error(n)}}(e)}))}catch(n){console.error(n)}}var O=!1;function m(){if(!O){O=!0;var n=e.scripts[0],r=e.createElement("script");r.src=a,r.crossOrigin="anonymous",r.addEventListener("load",E,{once:!0,passive:!0}),n.parentNode.insertBefore(r,n)}}function L(){var e=n.__SENTRY__;return!(void 0===e||!e.hub||!e.hub.getClient())}n[i]=n[i]||{},n[i].onLoad=function(n){L()?n():p.push(n)},n[i].forceLoad=function(){setTimeout((function(){m()}))},["init","addBreadcrumb","captureMessage","captureException","captureEvent","configureScope","withScope","showReportDialog"].forEach((function(e){n[i][e]=function(){y({f:e,a:arguments})}})),n.addEventListener(r,g),n.addEventListener(t,h),u||setTimeout((function(){m()}))}
(
!(function (n, e, r, t, i, o, a, c, s) {
for (var u = s, f = 0; f < document.scripts.length; f++)
if (document.scripts[f].src.indexOf(o) > -1) {
u && 'no' === document.scripts[f].getAttribute('data-lazy') && (u = !1);
break;
}
var p = [];
function l(n) {
return 'e' in n;
}
function d(n) {
return 'p' in n;
}
function _(n) {
return 'f' in n;
}
var v = [];
function y(n) {
u &&
(l(n) || d(n) || (_(n) && n.f.indexOf('capture') > -1) || (_(n) && n.f.indexOf('showReportDialog') > -1)) &&
m(),
v.push(n);
}
function g() {
y({ e: [].slice.call(arguments) });
}
function h(n) {
y({ p: n });
}
function E() {
try {
n.SENTRY_SDK_SOURCE = 'loader';
var e = n[i],
o = e.init;
(e.init = function (i) {
n.removeEventListener(r, g), n.removeEventListener(t, h);
var a = c;
for (var s in i) Object.prototype.hasOwnProperty.call(i, s) && (a[s] = i[s]);
!(function (n, e) {
var r = n.integrations || [];
if (!Array.isArray(r)) return;
var t = r.map(function (n) {
return n.name;
});
n.tracesSampleRate &&
-1 === t.indexOf('BrowserTracing') &&
(e.BrowserTracing
? r.push(new e.BrowserTracing())
: e.browserTracingIntegration && r.push(e.browserTracingIntegration()));
(n.replaysSessionSampleRate || n.replaysOnErrorSampleRate) &&
-1 === t.indexOf('Replay') &&
(e.Replay ? r.push(new e.Replay()) : e.replayIntegration && r.push(e.replayIntegration()));
n.integrations = r;
})(a, e),
o(a);
}),
setTimeout(function () {
return (function (e) {
try {
'function' == typeof n.sentryOnLoad && (n.sentryOnLoad(), (n.sentryOnLoad = void 0));
for (var r = 0; r < p.length; r++) 'function' == typeof p[r] && p[r]();
p.splice(0);
for (r = 0; r < v.length; r++) {
_((o = v[r])) && 'init' === o.f && e.init.apply(e, o.a);
}
L() || e.init();
var t = n.onerror,
i = n.onunhandledrejection;
for (r = 0; r < v.length; r++) {
var o;
if (_((o = v[r]))) {
if ('init' === o.f) continue;
e[o.f].apply(e, o.a);
} else l(o) && t ? t.apply(n, o.e) : d(o) && i && i.apply(n, [o.p]);
}
} catch (n) {
console.error(n);
}
})(e);
});
} catch (n) {
console.error(n);
}
}
var O = !1;
function m() {
if (!O) {
O = !0;
var n = e.scripts[0],
r = e.createElement('script');
(r.src = a),
(r.crossOrigin = 'anonymous'),
r.addEventListener('load', E, { once: !0, passive: !0 }),
n.parentNode.insertBefore(r, n);
}
}
function L() {
var e = n.__SENTRY__;

// TODO: This is a temporary hack to make the loader script compatible with the versioned
// carrier. This needs still needs to be added to the actual loader script before we
// release the loader for v8!
var v = e && e.version && e[e.version];

return !(void 0 === e || !e.hub || !e.hub.getClient()) || !!v;
}
(n[i] = n[i] || {}),
(n[i].onLoad = function (n) {
L() ? n() : p.push(n);
}),
(n[i].forceLoad = function () {
setTimeout(function () {
m();
});
}),
[
'init',
'addBreadcrumb',
'captureMessage',
'captureException',
'captureEvent',
'configureScope',
'withScope',
'showReportDialog',
].forEach(function (e) {
n[i][e] = function () {
y({ f: e, a: arguments });
};
}),
n.addEventListener(r, g),
n.addEventListener(t, h),
u ||
setTimeout(function () {
m();
});
})(
window,
document,
'error',
Expand All @@ -8,5 +142,5 @@
'loader.js',
__LOADER_BUNDLE__,
__LOADER_OPTIONS__,
__LOADER_LAZY__
__LOADER_LAZY__,
);
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@ window.sentryIsLoaded = () => {
const __sentry = window.__SENTRY__;

// If there is a global __SENTRY__ that means that in any of the callbacks init() was already invoked
return !!(!(typeof __sentry === 'undefined') && __sentry.hub && __sentry.hub.getClient());
return !!(
!(typeof __sentry === 'undefined') &&
__sentry.version &&
!!__sentry[__sentry.version]
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,20 @@ Sentry.metrics.gauge('gauge', 5);
Sentry.metrics.gauge('gauge', '15');
Sentry.metrics.set('set', 'nope');
Sentry.metrics.set('set', 'another');

Sentry.metrics.timing('timing', 99, 'hour');
Sentry.metrics.timing('timingSync', () => {
sleepSync(200);
});
Sentry.metrics.timing('timingAsync', async () => {
await new Promise(resolve => setTimeout(resolve, 200));
});

function sleepSync(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if (new Date().getTime() - start > milliseconds) {
break;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,18 @@ sentryTest('collects metrics', async ({ getLocalTestUrl, page }) => {
const statsdBuffer = await getFirstSentryEnvelopeRequest<Uint8Array>(page, url, properEnvelopeRequestParser);
const statsdString = new TextDecoder().decode(statsdBuffer);
// Replace all the Txxxxxx to remove the timestamps
const normalisedStatsdString = statsdString.replace(/T\d+\n?/g, 'T000000');
const normalisedStatsdString = statsdString.replace(/T\d+\n?/g, 'T000000').trim();

expect(normalisedStatsdString).toEqual(
'increment@none:6|c|T000000distribution@none:42:45|d|T000000gauge@none:15:5:15:20:2|g|T000000set@none:3387254:3443787523|s|T000000',
);
const parts = normalisedStatsdString.split('T000000');

expect(parts).toEqual([
'increment@none:6|c|',
'distribution@none:42:45|d|',
'gauge@none:15:5:15:20:2|g|',
'set@none:3387254:3443787523|s|',
'timing@hour:99|d|',
expect.stringMatching(/timingSync@second:0.(\d+)\|d\|/),
expect.stringMatching(/timingAsync@second:0.(\d+)\|d\|/),
'', // trailing element
]);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
tracesSampleRate: 1.0,
release: '1.0.0',
autoSessionTracking: false,
});

window.timingSync = () => {
// Ensure we always have a wrapping span
return Sentry.startSpan({ name: 'manual span' }, () => {
return Sentry.metrics.timing('timingSync', () => {
sleepSync(200);
return 'sync done';
});
});
};

window.timingAsync = () => {
// Ensure we always have a wrapping span
return Sentry.startSpan({ name: 'manual span' }, () => {
return Sentry.metrics.timing('timingAsync', async () => {
await new Promise(resolve => setTimeout(resolve, 200));
return 'async done';
});
});
};

function sleepSync(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if (new Date().getTime() - start > milliseconds) {
break;
}
}
}
Loading
Loading