captbaritone / raven-for-redux

A Raven middleware for Redux
295 stars 25 forks source link

Add support for @sentry/browser #93

Closed pkuczynski closed 4 years ago

pkuczynski commented 6 years ago

@sentry/browser is the latest version of Sentry SDK. I tried to upgrade my project, but my application fails to run with an error Raven.setDataCallback is not a function. That's because the Sentry API changed significantly and this library would have to be updated...

deammer commented 5 years ago

See #82.

pkuczynski commented 5 years ago

Have seen this and finally implemented this integration myself, dropping usage of this package

cihati commented 5 years ago

@pkuczynski care to share how you did it?

thomasporez-tinyclues commented 5 years ago

+1 @pkuczynski if you can show us something around this !

pkuczynski commented 5 years ago

Sure!

I created a new file in src/store/middlewares/sentry.js:

import * as Sentry from '@sentry/browser'

const sentry = (store) => {
  Sentry.configureScope((scope) => {
    scope.addEventProcessor((event) => {
      // line below assumes that username is stores under user in the state
      const { user: { username }, ...state } = store.getState()

      return {
        ...event,
        extra: {
          ...event.extra,
          'redux:state': state
        },
        user: {
          ...event.user,
          username
        }
      }
    })
  })

  return next => (action) => {
    Sentry.addBreadcrumb({
      category: 'redux-action',
      message: action.type
    })

    return next(action)
  }
}

export default sentry

then I also had one place where I define all my middlewares src/store/middlewares/sentry.js:

import { applyMiddleware } from 'redux'
...
import sentry from './sentry'

const middlewares = applyMiddleware(
  ...
  sentry
)

export default middlewares

and then in a place where I initialize store I had:

const store = createStore(
  reducers,
  compose(middlewares)
)
bcanseco commented 5 years ago

For anyone coming across @pkuczynski's awesome comment in the future:

If you can't seem to get redux:state to show up when capturing an exception using withScope later on in your app, you probably want to use addGlobalEventProcessor.

- Sentry.configureScope((scope) => {
-   scope.addEventProcessor((event) => {
+ Sentry.addGlobalEventProcessor((event) => {
OliverJAsh commented 5 years ago

@pkuczynski Your suggested code seems to be the way forward! Have you thought about wrapping it up into a separate package? :-D

pkuczynski commented 5 years ago

@OliverJAsh I could if you promise to use it ;)

OliverJAsh commented 5 years ago

Haha, I promise! That plus 27 other ❤️s!

olavoasantos commented 5 years ago

Not sure if this works 100% like the current raven-for-redux but I tried to work based on the current implementation. I tested on the example and apparently it did ok... But I didn't refactor the tests to check.

const identity = x => x;
const getUndefined = () => {};
const getType = action => action.type;
const filter = () => true;
function createSentryMiddleware(Sentry, options = {}) {
  // TODO: Validate options.
  const {
    breadcrumbDataFromAction = getUndefined,
    breadcrumbMessageFromAction = getType,
    actionTransformer = identity,
    stateTransformer = identity,
    breadcrumbCategory = "redux-action",
    filterBreadcrumbActions = filter,
    getUserContext,
    getTags
  } = options;

  let lastAction;
  return store => {
    Sentry.configureScope((scope) => {
      scope.addEventProcessor((event) => {
        const state = store.getState();

        const user = getUserContext
          ? Object.assign({}, event.user, getUserContext(state))
          : event.user;

        const tag = getTags
          ? Object.assign({}, event.tag, getTags(state))
          : event.tag;

        const extra = Object.assign({}, event.extra, {
          state: stateTransformer(state),
          lastAction: actionTransformer(lastAction),
        });

        return Object.assign({}, event, { extra, user, tag });
      });
    });

    return next => action => {
      // Log the action taken to Sentry so that we have narrative context in our
      // error report.
      if (filterBreadcrumbActions(action)) {
        Sentry.addBreadcrumb({
          category: breadcrumbCategory,
          message: breadcrumbMessageFromAction(action),
          data: breadcrumbDataFromAction(action)
        });
      }

      lastAction = action;
      return next(action);
    };
  }
}

module.exports = createSentryMiddleware;
olavoasantos commented 5 years ago

For reference, the above implementation using the scope.addEventProcessor method gets called when sentry is about to send the error and not when the error is thrown. Since Redux keeps on working, you end up getting misleading information as there are breadcrumbs (i.e. actions) which were emitted after the exception was thrown.

To avoid this, you can store the state, action and breadcrumbs from when the Sentry.lastEventId() is changed (which means an error was thrown) and then using this stored data to modify the scope using addEventProcessor.

You can check out this gist: https://gist.github.com/olavoasantos/9ac791098758ee7dedf0c0424ec8b398

kumar303 commented 4 years ago

This claims to be a rewrite of raven-for-redux with support for @sentry/browser : https://github.com/vidit-sh/redux-sentry-middleware

I haven't tried it yet 🤞

kalemi19 commented 4 years ago

This claims to be a rewrite of raven-for-redux with support for @sentry/browser : https://github.com/vidit-sh/redux-sentry-middleware

I haven't tried it yet 🤞

I tried it briefly and it seems to be working properly! 🎉

kumar303 commented 4 years ago

Yep, I can confirm that it is working well using @sentry/browser @ 5.7.0

captbaritone commented 4 years ago

https://github.com/vidit-sh/redux-sentry-middleware looks like the right approach. I'll update the readme to point people there.