initializers/exceptions.js

'use strict'

const os = require('os')
const ActionHero = require('./../index.js')
const api = ActionHero.api

/**
 * Handlers for when things go wrong.
 *
 * @namespace api.exceptions
 * @extends ActionHero.Initializer
 */
module.exports = class Exceptions extends ActionHero.Initializer {
  constructor () {
    super()
    this.name = 'exceptions'
    this.loadPriority = 130
  }

  relevantDetails () {
    return ['action', 'remoteIP', 'type', 'params', 'room']
  }

  initialize () {
    api.exceptionHandlers = {}
    api.exceptionHandlers.reporters = []

    const consoleReporter = (error, type, name, objects, severity) => {
      let extraMessages = []

      if (type === 'loader') {
        extraMessages.push('! Failed to load ' + objects.fullFilePath)
      } else if (type === 'action') {
        extraMessages.push('! uncaught error from action: ' + name)
        extraMessages.push('! connection details:')
        let relevantDetails = this.relevantDetails()
        for (let i in relevantDetails) {
          let relevantDetail = relevantDetails[i]
          if (
            objects.connection[relevantDetail] !== null &&
            objects.connection[relevantDetail] !== undefined &&
            typeof objects.connection[relevantDetail] !== 'function'
          ) {
            extraMessages.push('!     ' + relevantDetail + ': ' + JSON.stringify(objects.connection[relevantDetail]))
          }
        }
      } else if (type === 'task') {
        extraMessages.push('! error from task: ' + name + ' on queue ' + objects.queue + ' (worker #' + objects.workerId + ')')
        try {
          extraMessages.push('!     arguments: ' + JSON.stringify(objects.task.args))
        } catch (e) {}
      } else {
        extraMessages.push('! Error: ' + error.message)
        extraMessages.push('!     Type: ' + type)
        extraMessages.push('!     Name: ' + name)
        extraMessages.push('!     Data: ' + JSON.stringify(objects))
      }

      for (let m in extraMessages) {
        api.log(extraMessages[m], severity)
      }
      let lines
      try {
        lines = error.stack.split(os.EOL)
      } catch (e) {
        lines = new Error(error).stack.split(os.EOL)
      }
      for (let l in lines) {
        let line = lines[l]
        api.log('! ' + line, severity)
      }
      api.log('*', severity)
    }

    api.exceptionHandlers.reporters.push(consoleReporter)

    api.exceptionHandlers.report = (error, type, name, objects, severity) => {
      if (!severity) { severity = 'error' }
      for (let i in api.exceptionHandlers.reporters) {
        api.exceptionHandlers.reporters[i](error, type, name, objects, severity)
      }
    }

    api.exceptionHandlers.loader = (fullFilePath, error) => {
      let name = 'loader:' + fullFilePath
      api.exceptionHandlers.report(error, 'loader', name, {fullFilePath: fullFilePath}, 'alert')
    }

    api.exceptionHandlers.action = (error, data, next) => {
      let simpleName
      try {
        simpleName = data.action
      } catch (e) {
        simpleName = error.message
      }
      let name = 'action:' + simpleName
      api.exceptionHandlers.report(error, 'action', name, {connection: data.connection}, 'error')
      data.connection.response = {} // no partial responses
      if (typeof next === 'function') { next() }
    }

    api.exceptionHandlers.task = (error, queue, task, workerId) => {
      let simpleName
      try {
        simpleName = task['class']
      } catch (e) {
        simpleName = error.message
      }
      let name = 'task:' + simpleName
      api.exceptionHandlers.report(error, 'task', name, {task: task, queue: queue, workerId: workerId}, api.config.tasks.workerLogging.failure)
    }
  }
}