UNPKG

@naturalcycles/nodejs-lib

Version:
139 lines (138 loc) 4.95 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.slackDefaultMessagePrefixHook = exports.SlackService = void 0; const js_lib_1 = require("@naturalcycles/js-lib"); const got_1 = require("got"); const __1 = require(".."); const GAE = !!process.env['GAE_INSTANCE']; const DEFAULTS = { username: 'bot', channel: '#log', icon_emoji: ':spider_web:', items: 'no text', }; const INSPECT_OPT = { colors: false, includeErrorData: true, includeErrorStack: true, }; /** * Has 2 main methods: * * 1. .send({ items: any, channel: ..., ... }) * Low-level method with all possible options available. * * 2. .log(...items: any[]) * Shortcut method to "just log a bunch of things", everything is "by default" there. * * .send method has a shortcut: * .send(string, ctx?: CTX) */ class SlackService { constructor(cfg) { this.cfg = { messagePrefixHook: slackDefaultMessagePrefixHook, logger: console, ...cfg, inspectOptions: { ...INSPECT_OPT, ...cfg.inspectOptions, }, }; } /** * Allows to "log" many things at once, similar to `console.log(one, two, three). */ async log(...items) { await this.send({ // If it's an Array of just 1 item - treat it as non-array items: items.length === 1 ? items[0] : items, }); } async send(input, ctx) { const { webhookUrl, messagePrefixHook, inspectOptions } = this.cfg; // If String is passed as first argument - just transform it to a full SlackMessage const msg = typeof input === 'string' ? { items: input } : input; if (ctx !== undefined) { Object.assign(msg, { ctx }); } this.cfg.logger.log(...[msg.items, msg.kv, msg.attachments, msg.mentions].filter(Boolean)); if (!webhookUrl) return; // Transform msg.kv into msg.attachments if (msg.kv) { ; (msg.attachments || (msg.attachments = [])).push({ fields: this.kvToFields(msg.kv) }); delete msg.kv; // to not pass it all the way to Slack Api } let text; // Array has a special treatment here if (Array.isArray(msg.items)) { text = msg.items.map(t => (0, __1.inspectAny)(t, inspectOptions)).join('\n'); } else { text = (0, __1.inspectAny)(msg.items, inspectOptions); } // Wrap in markdown-text-block if it's anything but plain String if (typeof msg.items !== 'string') { text = '```' + text + '```'; } if (msg.mentions?.length) { text += '\n' + msg.mentions.map(s => `<@${s}>`).join(' '); } const prefix = await messagePrefixHook(msg); if (prefix === null) return; // filtered out! const json = (0, js_lib_1._omit)({ ...DEFAULTS, ...this.cfg.defaults, ...msg, // Text with Prefix text: [prefix.join(': '), text].filter(Boolean).join('\n'), }, ['items', 'ctx']); await got_1.default .post(webhookUrl, { json, responseType: 'text', timeout: 90000, }) .catch(err => { // ignore (unless throwOnError is set) if (msg.throwOnError) throw err; }); } kvToFields(kv) { return Object.entries(kv).map(([k, v]) => ({ title: k, value: String(v), short: String(v).length < 80, })); } /** * Returns a CommonLogger implementation based on this SlackService instance. */ getCommonLogger(opt) { const { minLogLevel = 'log', logChannel, warnChannel, errorChannel } = opt; const defaultChannel = this.cfg.defaults?.channel || DEFAULTS.channel; const q = new js_lib_1.PQueue({ concurrency: 1, }); return (0, js_lib_1.commonLoggerMinLevel)({ log: (...args) => q.push(() => this.send({ items: args, channel: logChannel || defaultChannel })), warn: (...args) => q.push(() => this.send({ items: args, channel: warnChannel || defaultChannel })), error: (...args) => q.push(() => this.send({ items: args, channel: errorChannel || defaultChannel })), }, minLogLevel); } } exports.SlackService = SlackService; function slackDefaultMessagePrefixHook(msg) { const tokens = [(0, js_lib_1.localTime)().toPretty()]; const { ctx } = msg; // AppEngine-specific decoration if (GAE && ctx && typeof ctx === 'object' && typeof ctx.header === 'function') { tokens.push(ctx.header('x-appengine-country'), ctx.header('x-appengine-city')); } return tokens.filter(Boolean); } exports.slackDefaultMessagePrefixHook = slackDefaultMessagePrefixHook;