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

Best way to integrate Sentry #2334

Closed
lardissone opened this issue Jun 21, 2017 · 14 comments
Closed

Best way to integrate Sentry #2334

lardissone opened this issue Jun 21, 2017 · 14 comments

Comments

@lardissone
Copy link
Contributor

What's the best way to integrate Sentry to a next project?

@sergiodxa
Copy link
Contributor

Server side you may want to create a custom server, using the express connection will make it easier to integrate.

Client side you can load the JS lib in a custom pages/_document.js and run it, you can follow the example in Sentry docs on how to use it with React because you will need to handle error in render of each component. Maybe you can create a HOC for that.

@arunoda
Copy link
Contributor

arunoda commented Jun 22, 2017

Thanks @sergiodxa for answering.

@arunoda arunoda closed this as completed Jun 22, 2017
@ethanresnick
Copy link

This still does not work because of #1852, which really needs to be addressed.

@lardissone
Copy link
Contributor Author

@sergiodxa I've tried creating a HOC wrapping the Main component and it didn't worked. It returns Error: unmountComponentAtNode(...): Target container is not a DOM element.

import React from 'react'
import Raven from 'raven-js'
import config from '../config/app'

let logException = (ex, context) => { window.console && console.error && console.error(ex) }  // eslint-disable-line no-console
if (config.sentry) {
    Raven.config(config.sentry).install()

    logException = (ex, context) => {
        Raven.captureException(ex, {
            extra: context
        })
        window.console && console.error && console.error(ex) // eslint-disable-line no-console
    }
}

export default function withSentry(wrappedComponent) {
    return class SentryComponent extends React.Component {
        render() {
            try {
                return <wrappedComponent {...this.props} />
            } catch (ex) {
                logException(ex)
            }
        }
    }
}

And then in the _document.js I wrap it this way:

// ...
     render() {
          const Content = withSentry(Main)
          return ( // ...
               <Content />
          // ...
          )
     }
// ...

@lardissone
Copy link
Contributor Author

Finally resolved it by adding the withSentry HOC to each page instead of the _document.js. Not sure if it's the best way but works.

@oliviertassinari
Copy link
Contributor

oliviertassinari commented Jun 23, 2017

The problem

I confirm the issue, next.js is swallowing the errors thrown during the render of React.
For instance, the following hook is never called:

  window.onerror = function() {
    console.log('onerror')
  };

The errors are catch on the server and the client, also using this hook ReactReconciler.mountComponent. It's challenging catch the errors.

A possible solution

I ended up performing the following changes:

  • Aliasing raven to raven-js on the client for code sharing as the code needed to initialize raven-js on the client needs to be bundled for the server too.
      resolve: Object.assign({}, config.resolve, {
        alias: Object.assign({}, config.resolve.alias, {
          // raven for the server
          // raven-js for the client
          raven: 'raven-js',
        }),
      }),
  • Creating a server.js initialization script to bootstrap raven on the server. This is performed before bootstrapping the next.js request handler.
  • Creating a client.js initialization script to bootstrap raven-js on the client. This is performed at the page level. Also, we need to make sure it's bootstrapped only once.
  • Creating a common.js script to throw errors independently from the platform (client or server). I'm using a global internally to share the raven/raven-js object.
  • Wrapping the top level getInitialProps with a try {} catch (err) {} to throw errors. In my case, react-apollo is traversing the react tree (depth breadth first) in a higher-order component.
  • Hook into ReactReconciler.mountComponent to catch errors.
  • Remove the aliasing to the production version of react in order to be able to hook into the ReactReconciler.mountComponent.

@sebastianarena
Copy link

+1 for being able to track the errors in the way we need!

@lardissone
Copy link
Contributor Author

@oliviertassinari should we re-open the issue? I think the errors should be propagated to the client/server. Many tools may be affected by this (Sentry, Rollbar, New Relic, etc)

@oliviertassinari
Copy link
Contributor

@lardissone I would rather see next.js forwarding errors after handling them internally if possible. I'm doing the opposite right now, catching errors then forwarding them to next.js, that's more work on user space.

@oliviertassinari
Copy link
Contributor

oliviertassinari commented Jun 27, 2017

One last thing, I had to remove the production aliasing to the minified version of React in order to get the ReactReconciler.mountComponent hooks working.

        alias: Object.assign({}, config.resolve.alias, {
          // raven for the server
          // raven-js for the client
          raven: 'raven-js',
          // Fix for hijacking react-dom/lib/ReactReconciler
          react: 'react',
          'react-dom': 'react-dom',
        }),

@lardissone Back to your question. Yes, I think that we should be reopening this issue in order to expose a clean API. I believe that the amount of introspection needed into next.js to make it work is too high.

More specifically, having to hook into ReactReconciler.mountComponent on userland isn't great. I'm not sure what's the React Fiber story around error boundaries is, but I would rather not have to do it on userland (I only had to do it because next.js do it)

@mattfysh
Copy link

mattfysh commented Jul 3, 2017

This seems to be working well:

// pages/_error.js
import NextError from 'next/error'
import { logException } from '../src/utils/analytics'

export default class MyError extends NextError {
  static getInitialProps({ res, err }) {
    if (!(err instanceof Error)) {
      err = new Error(err && err.message)
    }
    res ? Raven.captureException(err) : logException(err)
    return NextError.getInitialProps({ res, err })
  }
}

Some notes:

  • res is truthy in SSR only
  • set browser.raven to false in package.json to prevent it from being bundled for the client
  • this doesn't work so well when in dev mode, the err parameter seems inconsistent
  • if the error is SSR, the error will be deserialized into a POJO for the client. You can choose to ignore it (however here I'm doing an instanceof check and reporting it, so the same error is essentially reported twice - once for server and once for client)
  • logException is a wrapper around Raven.captureException using the CDN-hosted package. I've got a Custom Document that loads Raven before <NextScript />

Thoughts?

@kdby-io
Copy link

kdby-io commented Jul 5, 2017

@mattfysh How can I reach inside of MyError when a error is thrown on client side?

I add res ? console.log('server side error') : console.log('client side error'); in getInitialProps, but I can get 'server side error' only.

When client side error thrown, Sentry get the error. But I think this is not related with MyError.

@mattfysh
Copy link

mattfysh commented Jul 5, 2017

@pueue how are you navigating to the page? If you're doing full page refresh/load of the page, you'll get the server side error. To trigger the client-side error, you need to navigate from another page, to the page with the error, using a <Link> or similar.

@kdby-io
Copy link

kdby-io commented Jul 5, 2017

@mattfysh This is a example.

// pages/page1.js
export default Page1 extends React.Component {
  ...
  throwError() {
    throw new Error('client side eventHandling error');
  }

  render() {
    return (
      <div>
        <Link href='/page2'>Link</Link><br />
        <TextField onChange={this.throwError}/>
      </div>
    )
  }
}

// pages/page2.js
export default Page2 extends React.Component {
  ...
  render() {
    eval("throw new Error('client side rendering error')");
    return (
      <div>
      </div>
    );
  }
}

When I change something in TextField, a error is thrown and sentry catch it.
But when I click Link, a error is thrown and sentry cannot catch it.

Can I see your sample code using what you suggest? I need a help.

@lock lock bot locked as resolved and limited conversation to collaborators May 11, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants