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

Improve with-sentry example #5727

Merged
merged 6 commits into from
Dec 10, 2018
Merged

Conversation

sheerun
Copy link
Contributor

@sheerun sheerun commented Nov 21, 2018

In response to #1852

  • Ability to catch server-side errors
  • Ability to catch errors in custom server code
  • Deduplicating analogous server-side and client-side errors
  • Reporting correct number of users in sentry for each error
  • Reporting BUILD_ID as release in sentry
  • Ability to set SENTRY_TOKEN so sentry can access source maps in production

Is it complicated? Yes. Is it necessary for proper production error handling, also yes.

I propose you merge this and I could extract this into plugins when next.js supports creating plugins for Server and App

@sheerun
Copy link
Contributor Author

sheerun commented Nov 21, 2018

I think I'll replace session cookie with ip address so there are less packages in example

@sheerun
Copy link
Contributor Author

sheerun commented Nov 21, 2018

or never mind, I'll still need to persist ip_address in cookies to client so errors are properly reported if next is failing to load somehow, so the complexity will be the same

@Enalmada
Copy link
Contributor

Sentry working properly is a critical requirement before going to production with Next.js. I can't thank you enough for the time you put into this for the community.

@sheerun
Copy link
Contributor Author

sheerun commented Nov 21, 2018

It took a week or two but I'm pretty happy with error reporting now :)

For sure there are some things to improve:

I also recommend my targets-webpack-plugin so next.js websites are properly rendered by google bot (chrome 41) and legacy browsers (mobile browsers and internet explorer).

@timrogers
Copy link

❤️ Excited to try this!

@abraxxas
Copy link

Thanks a ton, for the time you put into this. Super excited to try this, this has stumped me for quite some time

@abraxxas
Copy link

abraxxas commented Nov 22, 2018

@sheerun are errors happening in getInitialProps on the server reported correctly for you? With this code for me they are still only reported the first time they are happening. subsequent errors of the same type get lost until i restart the server. Everything works perfectly with this setup

@abraxxas
Copy link

Is anyone else experiencing huge memory leaks on the node server (v: 10.13.0) with this example as long as it uses the sentry request and error handlers? Not sure if this is sentry related or because of interaction between sentry and next. I observed this using memwatch

    memwatch.on('leak', (info: any) => {
        console.log('memwatch::leak')
        console.error(info)
    })

    memwatch.on('stats', (stats: any) => {
        console.log('memwatch::stats')
        console.error(Util.inspect(stats, true, null))
    })

@sheerun
Copy link
Contributor Author

sheerun commented Nov 22, 2018

Maybe they are not reported because sentry ignores duplicated errors?

@Enalmada
Copy link
Contributor

@sheerun can you elaborate on the need for targets-webpack-plugin? If nextjs isn't properly rendering for googlebot that seems like a serious problem. I am curious if there are already nextjs issues open about that so we can work towards making nextjs work perfectly out of the box?

@sheerun
Copy link
Contributor Author

sheerun commented Nov 22, 2018

I don't know. By default it works with google bot but when you install practically any modern package it stops. The solution is to transcompile final bundles instead of each file individually.

@abraxxas
Copy link

I just double checked, it should not be because of duplicate errors. But client side error catching works perfectly now for us. I had to remove the sentry-handlers as they cause huge memory-leaks for us

@sheerun
Copy link
Contributor Author

sheerun commented Nov 23, 2018

For next week I won't have time to improve this PR to fix this so maybe someone else wants to


server.use(cookieParser())

server.use((req, res, next) => {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this part is needed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

why? it's needed for tracking users for errors in sentry

Copy link
Member

Choose a reason for hiding this comment

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

At the very least I don't think it should use an express middleware but use getInitialProps instead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it won't work if next.js crashes before of while excuting getIntialProps

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also I can't use getInitialProps because session id for user won't be preserved at all cases (e.g. when someone does full refresh of webpage). It would require reimplementing cookies in local storage.

// This attaches request information to sentry errors
server.use(Sentry.Handlers.requestHandler())

server.use(cookieParser())
Copy link
Member

Choose a reason for hiding this comment

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

Same for this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's needed for tracking users so we get same user id on client and on server

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also to preserve the same session id for different requests so users count for given error is not artificially high


server.get('*', (req, res) => handle(req, res))

// This handles errors if they are thrown before raching the app
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// This handles errors if they are thrown before raching the app
// This handles errors if they are thrown before reaching the app

pageProps = await Component.getInitialProps(ctx)
}
} catch (e) {
captureException(e, ctx)
Copy link
Member

@timneutkens timneutkens Nov 23, 2018

Choose a reason for hiding this comment

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

This should be moved to _error.js instead, we use something like this for zeit.co:

  static getInitialProps({ res, err }) {
    console.log({err})
    const statusCode = res ? res.statusCode : err ? err.statusCode : null
    Sentry.captureException(err)
    return { statusCode }
  }

Also passing ctx is dangerous when server errors happen, as it holds req and res, which can hold auth token cookies etc

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It doesn't really work and results in Synthetic errors. ctx is passed to 1st party captureException from which relevant data is extracted. I don't see how it's security issue

Choose a reason for hiding this comment

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

I tried lots of different ways to integrate sentry in the _error.js but all of them resulted in Synthetic errors

@iambouamar
Copy link

iambouamar commented Nov 29, 2018

when i did the same steps i get

These dependencies were not found:
console in ./node_modules/@sentry/node/dist/integrations/console.js
fs in ./node_modules/@sentry/utils/fs.js, ./node_modules/lsmod/index.js
module in ./node_modules/@sentry/node/dist/integrations/console.js, 
./node_modules/@sentry/node/dist/integrations/http.js

@Enalmada
Copy link
Contributor

Enalmada commented Nov 30, 2018

I tried this out and it seems to work great. No synthetic errors! I did also comment out the server.js sentry-handlers out of fear of the memory leak and everything on the test page still seems to work. I guess they are not really needed?
I wanted to get the dsn and environment from environment variables and for some reason the es6 method didn't work for me so I did what the transpiler would have done but I am sure there is a better way. Still, perhaps it helps someone who wants the same:

const _config = require("next/config");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj}; }
const _config2 = _interopRequireDefault(_config);
const _getConfig = (0, _config2.default)();
const publicRuntimeConfig = _getConfig.publicRuntimeConfig;

if (publicRuntimeConfig.SENTRY_DSN) {
    Sentry.init({
        dsn: publicRuntimeConfig.SENTRY_DSN,
        environment: publicRuntimeConfig.ENV,
        ...
    });
}

@sheerun
Copy link
Contributor Author

sheerun commented Nov 30, 2018

@Enalmada Don't say that. server.js role is twofold:

  1. It reports errors that are happening in custom server code and the code, outside of getInitialProps & render. Without it you only catch next.js-specific and react errors, not all errors. I had serious problems with my app because of errors that didn't get catched in custom server code and we potentially lost many of users because 500 pages were shown to them by express server before even reaching next.js handler, but no error reporting happened. You cannot just ignore it.
  2. For each error in sentry it reports number of users that are affected by this error by setting sid (session id) cookie. Anyone who maintains popular app knows there will be hundreds of random errors in sentry because of variety of web-enabled devices. Knowing number of affected users is critical for selecting actually important errors to fix.

Discovering memory leaks in sentry is saddening (getsentry/sentry-javascript#1762) but it doesn't disqualify this PR as proper solution for error handling. I hope Sentry will resolve these issues soon so we can merge this.

As for catching middleware errors this is proper solution for now (after memory leaks are fixed by sentry). If you don't like it you can iterate on it in the future by e.g. having next.js manage custom server code and only allow to inject middleware in next.config.js. This way next.js would be able to catch any errors even if they happened in custom server code.

As for tracking users for errors - there's no another way to implement this for now. Only using cookies guarantees that I can see all errors that happened to single user if he/she received errors in more than one page - it's not possible to track state between different requests without cookies. Not to mention even if using getInitialProps for setting sessionId somehow preserved sessionId between requests (as suggested by @timneutkens), it won't always work because of complexity of code that has to execute for this feature to work: successful getInitialProps, serialization, app initialization and then rehydration (and also because errors can happen in custom server code). In contrast setting cookie can happen by just setting header in middleware, and reading it is handled by browser itself - there's practically nothing that can break this process, and browser automatically cares to send session cookie for all future requests. It just makes no sense to reimplement this behavior in next.js using e.g. localStorage

@Enalmada
Copy link
Contributor

Enalmada commented Nov 30, 2018

Ah, thank you for the great explanation about how these two lines (which I prematurely commented out of memory leak fear) actually do more than I realized:
server.use(Sentry.Handlers.requestHandler()); server.use(Sentry.Handlers.errorHandler());
Everyone absolutely needs to catch all errors in addition to next errors!

@abraxxas issue notes that the memory leak only happens on node 10.x so this issue should still be merged asap, just with a strong README warning to use node 8.x until that sentry issue is resolved. The existing sentry example already has the sentry and request handler so merging this with a note to use node 8 is just one more way this pull request could help everyone.

Let's get this merged because it is so much better than current example and then we can continue to work with next to fundamentally improve the situation (middleware support, etc).

@abraxxas
Copy link

abraxxas commented Dec 6, 2018

I just implemented this in typescript and according to the types there are a few issues with this PR.

React.ErrorInfo only contains one key called componentStack and the request object does not have a params or query key

@sheerun
Copy link
Contributor Author

sheerun commented Dec 6, 2018

@abraxxas Maybe you want to send PR to this PR?

@abraxxas
Copy link

abraxxas commented Dec 6, 2018 via email

remove nonexisting keys from request and update errorInfo handling
@sheerun
Copy link
Contributor Author

sheerun commented Dec 6, 2018

I've merged @abraxxas PR to this PR so we should be fine now, thank you!

@timneutkens timneutkens merged commit cd1d364 into vercel:master Dec 10, 2018
@timneutkens
Copy link
Member

timneutkens commented Dec 10, 2018

I'm going to merge this but I think it would still be beneficial to have a lightweight version of this without a custom server / cookies / etc for if you only want to catch the errors that are rendering in _error.js (all user-side errors in Next.js), for example that's what we do for zeit.co.

@sheerun
Copy link
Contributor Author

sheerun commented Dec 11, 2018

@timneutkens Maybe something like static onErrorCatched() on _error.js could be made an API that catches errors from all places in next.js?

@sheerun
Copy link
Contributor Author

sheerun commented Dec 11, 2018

Maybe getInitialProps could be made the place but currently it suffers from few problems:

  1. Render of _error.js causes _app.js to render which could've triggered error in the first place. I think _error.js should be on the same level as _app.js so to prevent such often situation
  2. Because of something error passed to getInitialProps becomes synthetic and is missing proper stacktrace (maybe because getInitialProps of _error.js is async or maybe because async getInitialProps _app.js which it depends on is called, all the errors passed to onErrorCatched become synthetic or maybe because)
  3. It doesn't catch errors from getInitialProps of _app.js
  4. It doesn't catch errors that are thrown in custom server code (next.js would need to export app.getErrorHandler()) that should be attached at the end of custom server code.
  5. It doesn't catch unhandled errors in next.js app (probably new api like static onUnhandledException() would be needed

@Yankovsky
Copy link

Changes from this pr have been lost in merge commit 163830c

@Yankovsky
Copy link

Yankovsky commented Feb 19, 2019

@sheerun @timneutkens ^

@Timer
Copy link
Member

Timer commented Feb 20, 2019

Feel free to resubmit a PR @Yankovsky

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants