Redux Sentry Middleware

raw JSON →
0.2.2 verified Thu Apr 23 auth: no javascript abandoned

This package provides a Redux middleware designed to integrate Redux state and actions with Sentry's unified APIs (`@sentry/browser` and `@sentry/node`). It captures dispatched actions as Sentry breadcrumbs and attaches the last action and current Redux state as additional context to error reports. The package is a rewrite of `raven-for-redux` to support newer Sentry SDKs. The latest published version is 0.2.2, released on September 7, 2018, indicating it is no longer actively maintained and has been superseded by Sentry's official Redux integration, `Sentry.createReduxEnhancer`.

error TypeError: Sentry.captureException is not a function
cause The Sentry object passed to `createSentryMiddleware` is not properly initialized or is an incompatible version. This middleware expects a Sentry object with specific methods like `captureException`, `addBreadcrumb`, etc., typically from `@sentry/browser` or `@sentry/node`.
fix
Ensure Sentry.init() has been called successfully before creating the middleware and that you are using a version of @sentry/browser or @sentry/node compatible with this middleware (likely older versions, pre-v7).
error Error: Middleware is not a function
cause This error can occur if `createSentryMiddleware` is imported incorrectly (e.g., as a named import when it's a default export) or if the arguments passed to it are incorrect or missing, causing it to return an invalid middleware function.
fix
Confirm that createSentryMiddleware is imported as a default export (import createSentryMiddleware from 'redux-sentry-middleware';) and that it is called with the Sentry object as the first argument: createSentryMiddleware(Sentry, options?).
error Sentry events are missing Redux state or action context, or data is truncated.
cause The `breadcrumbDataFromAction`, `actionTransformer`, or `stateTransformer` options might be misconfigured, filtering out too much data, or returning data that exceeds Sentry's payload limits or type constraints (e.g., non-flat data for breadcrumbs).
fix
Review your actionTransformer and stateTransformer to ensure they return relevant, serializable data within Sentry's size limits. For breadcrumbDataFromAction, verify that the returned object is 'flat' (only string values). Consider increasing normalizeDepth in your Sentry.init call if your state is very deep and important for debugging.
breaking This package is considered abandoned and has not been updated since September 2018. It is built for older Sentry SDKs (`@sentry/browser` and `@sentry/node` versions from that era). Using it with modern Sentry SDKs (v7+) is likely to cause compatibility issues or miss out on new Sentry features. Sentry now provides an official `Sentry.createReduxEnhancer` for Redux integration.
fix Migrate to Sentry's official Redux integration, `Sentry.createReduxEnhancer`, which is actively maintained and designed for current Sentry SDK versions.
gotcha The `breadcrumbDataFromAction` option expects the returned `data` object to be 'flat' (values must be strings, not arrays or objects). Sending complex or deeply nested data here can lead to Sentry silently dropping the data or the entire event due to size limits.
fix Carefully transform your action data within `breadcrumbDataFromAction` to ensure it is a flat object with string values. Only include essential, non-sensitive information.
gotcha Both `actionTransformer` and `stateTransformer` should return serializable values. Attempting to send excessively large actions or state objects can exceed Sentry's payload limits, causing events to be dropped. Furthermore, ensure you do not mutate the original `action` or `state` objects within these transformer functions, as this can lead to unpredictable behavior in your Redux store.
fix Implement these transformers to filter or truncate large or sensitive data, returning a new, modified object rather than mutating the original. Increase Sentry's `normalizeDepth` during initialization if your state is genuinely deep and needs to be captured.
gotcha Order of middleware matters. `redux-sentry-middleware` should typically be placed after other middlewares that might intercept or emit actions (e.g., `redux-thunk`, `redux-promise`) to ensure it captures the final action being dispatched. However, it should be before any custom error-handling middleware if you intend Sentry to be the primary error reporter.
fix When applying middleware using `applyMiddleware`, ensure `redux-sentry-middleware` is positioned appropriately in the array based on your desired error reporting and action flow. The Sentry blog suggests it should be the first argument to `applyMiddleware` if you want it to capture errors from other middleware. However, the README for this specific package suggests preceding it with intercepting middlewares. This might imply different behavior than newer official Sentry enhancers. For modern Sentry integrations, `Sentry.createReduxEnhancer` is typically passed directly to `createStore` or `configureStore` as an enhancer, not via `applyMiddleware`.
npm install redux-sentry-middleware
yarn add redux-sentry-middleware
pnpm add redux-sentry-middleware

This quickstart demonstrates setting up `redux-sentry-middleware` with a Redux store, initializing Sentry, and using the middleware to capture actions and state. It includes examples of `breadcrumbDataFromAction`, `actionTransformer`, and `stateTransformer` to manage what data is sent to Sentry, including handling sensitive information and simulating an error.

import * as Sentry from '@sentry/browser';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import createSentryMiddleware from 'redux-sentry-middleware';

// Initialize Sentry SDK
Sentry.init({
  dsn: process.env.SENTRY_DSN ?? 'https://examplePublicKey@o0.ingest.sentry.io/0',
  tracesSampleRate: 1.0,
  // It's recommended to increase normalizeDepth for Redux states in Sentry
  normalizeDepth: 10 // Or however deep your state context needs to be
});

// Example Reducer
const initialState = { count: 0, user: { name: 'Guest', id: null } };
function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      // Simulate an error for testing
      if (action.payload === 'error') {
        throw new Error('Intentional Redux error on DECREMENT');
      }
      return { ...state, count: state.count - 1 };
    case 'SET_USER':
      return { ...state, user: action.payload };
    default:
      return state;
  }
}

const rootReducer = combineReducers({ counter: counterReducer });

// Create Sentry Middleware instance
const sentryMiddleware = createSentryMiddleware(Sentry, {
  breadcrumbDataFromAction: action => {
    // Log specific action data, ensuring it's flat and not too large
    if (action.type === 'SET_USER') {
      return { userId: action.payload.id, userName: action.payload.name };
    }
    return undefined;
  },
  actionTransformer: action => {
    // Remove sensitive data from actions before sending to Sentry
    if (action.type === 'SET_USER' && action.payload && action.payload.password) {
      const { password, ...safePayload } = action.payload;
      return { ...action, payload: safePayload };
    }
    return action;
  },
  stateTransformer: state => {
    // Remove sensitive data from state before sending to Sentry
    if (state.counter && state.counter.user && state.counter.user.sensitiveInfo) {
      const { sensitiveInfo, ...safeUser } = state.counter.user;
      return { ...state, counter: { ...state.counter, user: safeUser } };
    }
    return state;
  }
});

export const store = createStore(
  rootReducer,
  applyMiddleware(sentryMiddleware)
);

// Example usage
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'SET_USER', payload: { id: 123, name: 'Alice', password: 'secret' } });

try {
  store.dispatch({ type: 'DECREMENT', payload: 'error' });
} catch (e) {
  console.error('Caught expected error from Redux dispatch:', e.message);
}

console.log('Final state:', store.getState());