Created
November 11, 2016 20:30
-
-
Save joeybaker/3d48fbd1c24784c516bc77bae47a8c5e to your computer and use it in GitHub Desktop.
Redux middleware for bugsnag
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import requestAnimationFrame from 'fbjs/lib/requestAnimationFrame' | |
import {get, omit} from 'lodash' | |
// this is okay b/c we tell webpack to ignore the node version of bugsnag in | |
// package.json | |
// "browser": {"bugsnag": false} | |
import bugsnag from 'bugsnag' | |
const SEVERITIES = {ERROR: 'error', WARN: 'warning', INFO: 'info'} | |
const NODE_ENV = process.env.NODE_ENV | |
const IS_BROWSER = process.browser | |
const IS_PRODUCTION = NODE_ENV === 'production' | |
const STATUS_CODED_NOTFOUND = 404 | |
// actions that contain an error will go through this method, which parses them | |
// and masages the data into useful data for bugsnag | |
const parseErrorAction = (payload, error) => { | |
const {message, action} = payload | |
const lowerCaseMessage = typeof message === 'string' ? message.toLowerCase() : '' | |
// group actions together so we can debug them as a unit | |
payload.groupingHash = get(action, 'type') | |
if ( | |
lowerCaseMessage.includes('request failed') | |
|| lowerCaseMessage.includes('operation timed out') | |
|| lowerCaseMessage.includes('request timeout') | |
|| lowerCaseMessage.includes('probably because of a network issue') | |
|| lowerCaseMessage.includes('probably a network error') | |
) { | |
// we're going to set the bugsnag error name to a standard name, so store | |
// the action's name in metadata | |
payload.action || (payload.action = {}) | |
payload.name = 'Network Error' | |
payload.groupingHash = 'network error' | |
payload.severity = SEVERITIES.INFO | |
} | |
// these are user errors, we don't need to be notified of them | |
if ( | |
error.toString().includes('ValidationError') | |
|| error.toString().includes('Bad Request') | |
|| get(error, 'output.statusCode') === STATUS_CODED_NOTFOUND | |
) { | |
payload.groupingHash = 'user error' | |
payload.severity = SEVERITIES.INFO | |
} | |
// we can't do anything when 3rd party ad scripts error on an adblocker | |
if ( | |
lowerCaseMessage.includes('window.ormma') | |
|| lowerCaseMessage.includes('skypeclick2call') | |
) { | |
payload.groupingHash = 'ad blocker' | |
payload.severity = SEVERITIES.INFO | |
} | |
return payload | |
} | |
// This method receives error actions and sends them to bugsnag | |
const report = (error, type, meta = {}) => { | |
// set the groupingHash to group errors by the action type | |
const payload = parseErrorAction({ | |
action: {meta: omit(meta, 'password'), type} | |
, message: error.toString() | |
, severity: SEVERITIES.ERROR | |
, path: process.browser ? window.location.pathtype : '' | |
}, error) | |
const {bugsnagIgnore} = meta | |
if (IS_BROWSER) { | |
if (window.Bugsnag && IS_PRODUCTION && !bugsnagIgnore) { | |
window.Bugsnag.notifyException( | |
error | |
, `${type} ${payload.message}` | |
, payload | |
, payload.severity | |
) | |
} | |
else if (payload.severity === SEVERITIES.INFO) { | |
console.warn(type, payload, error) | |
} | |
else { | |
console.error(type, payload, error) | |
} | |
} | |
// if we've gotten here, we're on the server | |
else if (IS_PRODUCTION && !bugsnagIgnore){ | |
bugsnag.notify(error, payload) | |
} | |
// we only want to notify bugsang in production. In development, log the error | |
else { | |
console.error(type, payload, error) | |
} | |
} | |
const beforeNotify = () => { | |
// poll for the global Bugsnag object. Once found, attach the beforeNotify fn | |
// TODO: use requestIdleCallback when it's more widely supported | |
if (!window.Bugsnag) requestAnimationFrame(beforeNotify) | |
else window.Bugsnag.beforeNotify = parseErrorAction | |
} | |
if (process.browser && window.analytics) { | |
// ensure we configure bugsnag if it loads but another segment integration | |
// fails to load. | |
// TODO: use requestIdleCallback when it's more widely supported | |
requestAnimationFrame(beforeNotify) | |
// segment will load bugsang for us. It'll call `ready` when | |
// wait for analytics to be ready before trying to configure Bugsnag | |
window.analytics.ready(() => beforeNotify()) | |
} | |
export default () => (next) => (action = {payload: {}}) => { | |
// report every action to bugsnag as a bredcrumb | |
if (process.browser && window.Bugsnag && window.Bugsnag.leaveBreadcrumb) { | |
window.Bugsnag.leaveBreadcrumb(action.type, action.meta) | |
} | |
// if this is an error, report it to bugsnag | |
if (action.error) { | |
report(action.payload, action.type, action.meta) | |
} | |
// else, we're done, just move one | |
else { | |
return next(action) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment