import * as winston from "winston"; import * as nodemailer from "nodemailer"; import TransportStream from "winston-transport"; import Mail from "nodemailer/lib/mailer"; import { hasStackTrace } from "./async_utils"; import { inspect } from "util"; export function createLogger(opts?: { errorHandler?: (reason: unknown) => Error | undefined }) { process.on("unhandledRejection", (reason) => { if (opts?.errorHandler) { const c = opts.errorHandler(reason); if (c !== undefined) throw c; } if (hasStackTrace(reason)) throw new Error(`Unhandled rejection: ${reason}\nFull stack trace: ${reason.stack}`); let contents = `${reason}`; try { contents = inspect(reason, true, null); } catch (e) { // ignored } throw new Error(`Unhandled rejection: ${contents}`); }); const logger = winston.createLogger({ level: "debug", transports: [ new winston.transports.Console({ handleExceptions: true, level: "debug", debugStdout: true, format: winston.format.combine( winston.format.splat(), winston.format.prettyPrint(), winston.format.cli() ), }), ] }); if (process.env.NODE_ENV == "production" && process.env.GMAIL_NAME !== undefined && process.env.GMAIL_PASSWORD !== undefined) { logger.add(new EmailTransport({ level: "error", handleExceptions: true, format: winston.format.combine( winston.format.splat(), winston.format.simple() ) })); } return logger; } interface LogMessage { message: string; } class EmailTransport extends TransportStream { private mailer: Mail; constructor(opts?: TransportStream.TransportStreamOptions) { super(opts); this.mailer = nodemailer.createTransport({ host: "smtp.gmail.com", port: 465, secure: true, auth: { user: process.env.GMAIL_NAME, pass: process.env.GMAIL_PASSWORD } }); } log(info: LogMessage, next: () => void): void { setImmediate(() => { this.emit("logged", info); }); this.mailer.sendMail({ from: `${process.env.GMAIL_NAME}@gmail.com`, to: process.env.ERRORS_ADDR, subject: `Error at ${new Date().toISOString()}`, text: `Received error data: ${info.message}` }).catch(err => console.log(`Failed to send email! ${err}`)); next(); } }