Skip to content

Instantly share code, notes, and snippets.

@joeybaker
Created November 11, 2016 20:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joeybaker/3d48fbd1c24784c516bc77bae47a8c5e to your computer and use it in GitHub Desktop.
Save joeybaker/3d48fbd1c24784c516bc77bae47a8c5e to your computer and use it in GitHub Desktop.
Redux middleware for bugsnag
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