heroku
Version:
CLI to interact with Heroku
122 lines (121 loc) • 5.07 kB
JavaScript
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' }),
};
;