logging.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import * as winston_mongo from "winston-mongodb";
  2. import * as winston from "winston";
  3. import * as nodemailer from "nodemailer";
  4. import TransportStream from "winston-transport";
  5. import Mail from "nodemailer/lib/mailer";
  6. import { hasStackTrace } from "./async_utils";
  7. import { inspect } from "util";
  8. export function createEventLogger(opts: {
  9. host: string,
  10. username: string,
  11. password: string,
  12. name: string,
  13. }) {
  14. return winston.createLogger({
  15. level: "debug",
  16. format: winston.format.combine(
  17. winston.format.splat(),
  18. winston.format.prettyPrint(),
  19. ),
  20. transports: [
  21. new winston_mongo.MongoDB({
  22. db: `mongodb://${opts.username}:${opts.password}@${opts.host}`,
  23. tryReconnect: true,
  24. collection: "logs",
  25. label: opts.name,
  26. level: "debug",
  27. leaveConnectionOpen: false,
  28. })
  29. ]
  30. });
  31. }
  32. export function createLogger(opts?: {
  33. errorHandler?: (reason: unknown) => Error | undefined
  34. }) {
  35. process.on("unhandledRejection", (reason) => {
  36. if (opts?.errorHandler) {
  37. const c = opts.errorHandler(reason);
  38. if (c !== undefined)
  39. throw c;
  40. }
  41. if (hasStackTrace(reason))
  42. throw new Error(`Unhandled rejection: ${reason}\nFull stack trace: ${reason.stack}`);
  43. let contents = `${reason}`;
  44. try {
  45. contents = inspect(reason, true, null);
  46. } catch (e) {
  47. // ignored
  48. }
  49. throw new Error(`Unhandled rejection: ${contents}`);
  50. });
  51. const logger = winston.createLogger({
  52. level: "debug",
  53. transports: [
  54. new winston.transports.Console({
  55. handleExceptions: true,
  56. level: "debug",
  57. debugStdout: true,
  58. format: winston.format.combine(
  59. winston.format.splat(),
  60. winston.format.prettyPrint(),
  61. winston.format.cli()
  62. ),
  63. }),
  64. ]
  65. });
  66. if (process.env.NODE_ENV == "production" && process.env.GMAIL_NAME !== undefined && process.env.GMAIL_PASSWORD !== undefined) {
  67. logger.add(new EmailTransport({
  68. level: "error",
  69. handleExceptions: true,
  70. format: winston.format.combine(
  71. winston.format.splat(),
  72. winston.format.simple()
  73. )
  74. }));
  75. }
  76. return logger;
  77. }
  78. interface LogMessage {
  79. message: string;
  80. }
  81. class EmailTransport extends TransportStream {
  82. private mailer: Mail;
  83. constructor(opts?: TransportStream.TransportStreamOptions) {
  84. super(opts);
  85. this.mailer = nodemailer.createTransport({
  86. host: "smtp.gmail.com",
  87. port: 465,
  88. secure: true,
  89. auth: {
  90. user: process.env.GMAIL_NAME,
  91. pass: process.env.GMAIL_PASSWORD
  92. }
  93. });
  94. }
  95. log(info: LogMessage, next: () => void): void {
  96. setImmediate(() => {
  97. this.emit("logged", info);
  98. });
  99. this.mailer.sendMail({
  100. from: `${process.env.GMAIL_NAME}@gmail.com`,
  101. to: process.env.ERRORS_ADDR,
  102. subject: `Error at ${new Date().toISOString()}`,
  103. text: `Received error data: ${info.message}`
  104. }).catch(err => console.log(`Failed to send email! ${err}`));
  105. next();
  106. }
  107. }