From ef18b6dbeb9e8f1b614d0489aa731fb4081951a0 Mon Sep 17 00:00:00 2001 From: Mark Vayngrib Date: Wed, 25 Jul 2018 16:07:39 -0400 Subject: [PATCH] feat: email log alerts --- src/events.ts | 4 +++ .../lambda/log-alert-processor.ts | 15 +++++++-- src/in-house-bot/lambda/log-processor.ts | 6 ++-- src/in-house-bot/log-processor.ts | 33 ++++++++++++++++++- src/in-house-bot/types.d.ts | 6 ++++ 5 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/events.ts b/src/events.ts index 8bfceca4c..91f52d0fd 100644 --- a/src/events.ts +++ b/src/events.ts @@ -275,6 +275,10 @@ export const topics = { create: new EventTopic('user:create'), online: new EventTopic('user:online'), offline: new EventTopic('user:offline'), + }, + logging: { + logs: new EventTopic('logs:logs'), + alert: new EventTopic('logs:alert'), } } diff --git a/src/in-house-bot/lambda/log-alert-processor.ts b/src/in-house-bot/lambda/log-alert-processor.ts index f656a72a7..bc8970506 100644 --- a/src/in-house-bot/lambda/log-alert-processor.ts +++ b/src/in-house-bot/lambda/log-alert-processor.ts @@ -4,6 +4,7 @@ import { LogProcessor, parseLogAlertsTopicArn, fromLambda, + sendLogAlert, } from '../log-processor' const lambda = fromSNS({ event: LOG_ALERTS }) @@ -13,11 +14,21 @@ let processor: LogProcessor lambda.use(async (ctx) => { const { event, components } = ctx if (!processor) { + const conf = components.conf.bot.logging processor = fromLambda({ lambda, components }) - bot.hookSimple('logs:alerts', processor.handleAlertEvent) + bot.hook(bot.events.topics.logging.alert, async (ctx, next) => { + const alert = ctx.event = await processor.handleAlertEvent(ctx.event) + if (conf) { + await sendLogAlert({ bot, conf, alert }) + } else { + logger.debug('logging conf not present, not emailing anyone') + } + + await next() + }) } - await bot.fire('logs:alerts', event) + await bot.fire(bot.events.topics.logging.alert, event) }) export const handler = lambda.handler diff --git a/src/in-house-bot/lambda/log-processor.ts b/src/in-house-bot/lambda/log-processor.ts index b46f01b83..c986aa4b5 100644 --- a/src/in-house-bot/lambda/log-processor.ts +++ b/src/in-house-bot/lambda/log-processor.ts @@ -10,10 +10,12 @@ lambda.use(async (ctx) => { const { event, components } = ctx if (!processor) { processor = fromLambda({ lambda, components }) - bot.hookSimple('logs', processor.handleLogEvent) + bot.hook(bot.events.topics.logging.logs, async (ctx, next) => { + ctx.event = await processor.handleLogEvent(ctx.event) + }) } - await bot.fire('logs', event) + await bot.fire(bot.events.topics.logging.logs, event) }) export const handler = lambda.handler diff --git a/src/in-house-bot/log-processor.ts b/src/in-house-bot/log-processor.ts index c2da16e8c..b4f776276 100644 --- a/src/in-house-bot/log-processor.ts +++ b/src/in-house-bot/log-processor.ts @@ -9,6 +9,7 @@ import { TRADLE } from './constants' import { sha256 } from '../crypto' import { Env, + Bot, IPBLambda, SNSEvent, SNSEventRecord, @@ -17,6 +18,7 @@ import { IKeyValueStore, Logger, IBotComponents, + ILoggingConf, } from './types' import { Level, noopLogger } from '../logger' @@ -232,7 +234,8 @@ export class LogProcessor { const filename = getLogEventKey(event) const key = `${filename}.${this.ext}` this.logger.debug(`saving ${entries.length} entries to ${key}`) - await this.store.put(key, { ...event, entries }) + const formatted = { ...event, entries } + await this.store.put(key, formatted) } public handleAlertEvent = async (event: SNSEvent) => { @@ -246,6 +249,7 @@ export class LogProcessor { } await this.saveAlertEvent(parsed) + return parsed } public saveAlertEvent = async (event: ParsedAlertEvent) => { @@ -479,3 +483,30 @@ export const getAlertEventKey = (event: ParsedAlertEvent) => { // export const getLogsFolder = (env: Env) => `$logs/{env.STAGE}` // export const getAlertsFolder = (env: Env) => `alerts/${env.STAGE}` + +export const sendLogAlert = async ({ bot, conf, alert }: { + bot: Bot + conf: ILoggingConf + alert: ParsedAlertEvent +}) => { + const { senderEmail, destinationEmails } = conf + const body = JSON.stringify(alert, null, 2) + await bot.mailer.send({ + subject: 'logging alert', + from: senderEmail, + to: destinationEmails, + body, + format: 'text', + }) +} + +export const validateConf = async ({ bot, conf }: { + bot: Bot + conf: ILoggingConf +}) => { + const { senderEmail, destinationEmails } = conf + const resp = await bot.mailer.canSendFrom(senderEmail) + if (!resp.result) { + throw new Errors.InvalidInput(resp.reason) + } +} diff --git a/src/in-house-bot/types.d.ts b/src/in-house-bot/types.d.ts index 2f4864580..dea4c8363 100644 --- a/src/in-house-bot/types.d.ts +++ b/src/in-house-bot/types.d.ts @@ -58,12 +58,18 @@ export interface KVMap { [key: string]: any } +export interface ILoggingConf { + senderEmail: string + destinationEmails: string[] +} + export interface IBotConf { products: IProductsConf tours?: ITours sandbox?: boolean graphqlAuth?: boolean credentials?: KVMap + logging?: ILoggingConf // exposed directly in /info // publicConfig: any }