UNPKG

heroku

Version:

CLI to interact with Heroku

122 lines (121 loc) 5.07 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const color_1 = require("@heroku-cli/color"); const command_1 = require("@heroku-cli/command"); const core_1 = require("@oclif/core"); const heroku_cli_util_1 = require("@heroku/heroku-cli-util"); const lodash_1 = require("lodash"); const error_info_1 = require("../../lib/apps/error_info"); const colorize = (level, s) => { switch (level) { case 'critical': return color_1.default.red(s); case 'warning': return color_1.default.yellow(s); case 'info': return color_1.default.cyan(s); default: return s; } }; function buildErrorTable(errors, source) { return Object.keys(errors).map(name => { const count = errors[name]; const info = error_info_1.default.find(e => e.name === name); if (info) { return { name, count, source, level: info.level, title: info.title }; } return { name, count, source, level: 'critical', title: 'unknown error' }; }); } const sumErrors = (errors) => { const summed = {}; Object.keys(errors.data).forEach(key => { summed[key] = (0, lodash_1.sum)(errors.data[key]); }); return summed; }; class Errors extends command_1.Command { async run() { const { flags } = await this.parse(Errors); const hours = Number.parseInt(flags.hours, 10); const NOW = new Date().toISOString(); const YESTERDAY = new Date(Date.now() - (hours * 60 * 60 * 1000)).toISOString(); const DATE_QUERY = `start_time=${YESTERDAY}&end_time=${NOW}&step=1h`; async function getAllDynoErrors(types) { const values = await Promise.all(types.map(dynoErrors)); const memo = {}; types.forEach((key, index) => { memo[key] = values[index]; }); return memo; } const routerErrors = () => { return this.heroku.get(`/apps/${flags.app}/router-metrics/errors?${DATE_QUERY}&process_type=web`, { hostname: 'api.metrics.herokai.com', }).then(({ body }) => sumErrors(body)); }; const dynoErrors = (type) => { return this.heroku.get(`/apps/${flags.app}/formation/${type}/metrics/errors?${DATE_QUERY}`, { hostname: 'api.metrics.herokai.com', }).catch(error => { const { http } = error; // eslint-disable-next-line prefer-regex-literals const match = new RegExp('^invalid process_type provided', 'i'); if (http && http.statusCode === 400 && http.body && http.body.message && match.test(http.body.message)) { return { body: { data: {} } }; } throw error; }).then(rsp => { const { body } = rsp; return sumErrors(body); }); }; const { body: formation } = await this.heroku.get(`/apps/${flags.app}/formation`); const types = formation.map((p) => p.type); const showDyno = flags.dyno || !flags.router; const showRouter = flags.router || !flags.dyno; const noDynoEmpty = Promise.resolve({}); const noRouterEmpty = Promise.resolve({}); const [dyno, router] = await Promise.all([ showDyno ? getAllDynoErrors(types) : noDynoEmpty, showRouter ? routerErrors() : noRouterEmpty, ]); const errors = { dyno, router, }; if (flags.json) { heroku_cli_util_1.hux.styledJSON(errors); } else { let t = buildErrorTable(errors.router, 'router'); for (const type of Object.keys(errors.dyno)) { t = t.concat(buildErrorTable(dyno[type], type)); } if (t.length === 0) { core_1.ux.log(`No errors on ${color_1.default.app(flags.app)} in the last ${hours} hours`); } else { heroku_cli_util_1.hux.styledHeader(`Errors on ${color_1.default.app(flags.app)} in the last ${hours} hours`); heroku_cli_util_1.hux.table(t, { source: {}, name: { get: ({ name, level }) => colorize(level, name) }, level: { get: ({ level }) => colorize(level, level) }, title: { header: 'Desc' }, count: {}, }); } } } } exports.default = Errors; Errors.description = 'view app errors'; Errors.flags = { app: command_1.flags.app({ required: true }), remote: command_1.flags.remote(), json: command_1.flags.boolean({ description: 'output in json format' }), hours: command_1.flags.string({ description: 'number of hours to look back (default 24)', default: '24' }), router: command_1.flags.boolean({ description: 'show only router errors' }), dyno: command_1.flags.boolean({ description: 'show only dyno errors' }), };