Browse Source

Add logging to webserver

ghorsington 3 years ago
parent
commit
12a59d0d41

+ 9 - 83
bot/src/logging.ts

@@ -1,86 +1,12 @@
-import winston from "winston";
-import nodemailer from "nodemailer";
-import TransportStream from "winston-transport";
-import Mail from "nodemailer/lib/mailer";
-import { hasStackTrace, isHttpError } from "@shared/common/async_utils";
-import { inspect } from "util";
+import { createLogger } from "@shared/common/logging";
+import { isHttpError } from "@shared/common/async_utils";
 import { HTTPError } from "got/dist/source";
 
-export 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()
-            ),
-        }),
-    ]
-});
-
-process.on("unhandledRejection", (reason) => {
-    
-    if (isHttpError<HTTPError>(reason))
-        throw new Error(`HTTPError: ${reason.request.requestUrl} failed because ${reason}\nStack trace: ${reason.stack}`);
-    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}`); 
-});
-
-interface LogMessage {
-    message: string;       
-}
-
-class EmailTransport extends TransportStream {
-    private mailer: Mail;
-
-    constructor(opts?: TransportStream.TransportStreamOptions) {
-        super(opts);
-        console.log(`Username: ${process.env.GMAIL_NAME}`);
-        this.mailer = nodemailer.createTransport({
-            host: "smtp.gmail.com",
-            port: 465,
-            secure: true,
-            auth: {
-                user: process.env.GMAIL_NAME,
-                pass: process.env.GMAIL_PASSWORD
-            }
-        });
+export const logger = createLogger({
+    errorHandler: (reason) => {
+        if (isHttpError<HTTPError>(reason)) {
+            return new Error(`HTTPError: ${reason.request.requestUrl} failed because ${reason}\nStack trace: ${reason.stack}`);
+        }
+        return undefined;
     }
-
-    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();
-    }
-}
-
-if (process.env.NODE_ENV == "production") {
-    logger.add(new EmailTransport({
-        level: "error",
-        handleExceptions: true,
-        format: winston.format.combine(
-            winston.format.splat(),
-            winston.format.simple()
-        )
-    }));
-}
+});

+ 7 - 1
shared/package.json

@@ -11,8 +11,14 @@
   "license": "ISC",
   "dependencies": {
     "@types/node": "^14.0.5",
+    "nodemailer": "^6.4.11",
     "rimraf": "^3.0.2",
     "typeorm": "^0.2.25",
-    "typescript": "^3.9.3"
+    "typescript": "^3.9.3",
+    "winston": "^3.3.3",
+    "winston-transport": "^4.4.0"
+  },
+  "devDependencies": {
+    "@types/nodemailer": "^6.4.0"
   }
 }

+ 92 - 0
shared/src/common/logging.ts

@@ -0,0 +1,92 @@
+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();
+    }
+}

+ 1 - 1
shared/tsconfig.json

@@ -1,7 +1,7 @@
 {
     "compileOnSave": true,
     "compilerOptions": {
-        "module": "commonjs",
+        "module": "CommonJS",
         "noImplicitAny": true,
         "removeComments": true,
         "preserveConstEnums": true,

+ 6 - 2
web/package.json

@@ -35,9 +35,12 @@
 		"got": "^11.5.2",
 		"make-error": "^1.3.6",
 		"node-fetch": "^2.6.0",
+		"nodemailer": "^6.4.11",
 		"rpc_ts": "^2.1.0",
 		"sirv": "^1.0.5",
-		"svelte-awesome": "^2.3.0"
+		"svelte-awesome": "^2.3.0",
+		"winston": "^3.3.3",
+		"winston-transport": "^4.4.0"
 	},
 	"devDependencies": {
 		"@babel/core": "^7.11.1",
@@ -57,6 +60,7 @@
 		"@types/compression": "^1.7.0",
 		"@types/express": "^4.17.7",
 		"@types/node-fetch": "^2.5.7",
+		"@types/nodemailer": "^6.4.0",
 		"@typescript-eslint/eslint-plugin": "^3.10.1",
 		"@typescript-eslint/parser": "^3.10.1",
 		"autoprefixer": "^9.8.6",
@@ -72,7 +76,7 @@
 		"postcss-import": "^12.0.1",
 		"postcss-load-config": "^2.1.0",
 		"reflect-metadata": "^0.1.13",
-		"rollup": "^2.23.1",
+		"rollup": "^2.26.7",
 		"rollup-plugin-svelte": "^5.2.3",
 		"rollup-plugin-terser": "^7.0.0",
 		"sapper": "^0.28.0",

+ 9 - 4
web/rollup.config.js

@@ -8,6 +8,7 @@ import babel from "@rollup/plugin-babel";
 import { terser } from "rollup-plugin-terser";
 import config from "sapper/config/rollup";
 import alias from "@rollup/plugin-alias";
+import * as path from "path";
 import pkg from "./package.json";
 
 const preprocess = [
@@ -79,7 +80,7 @@ export default {
 
     server: {
         input: { server: config.server.input().server.replace(/\.js$/, ".ts") },
-        output: { ...config.server.output(), sourcemap },
+        output: { ...config.server.output(), sourcemap, interop: "auto" },
         plugins: [
             replace({
                 "process.browser": false,
@@ -96,16 +97,18 @@ export default {
             }),
             alias({
                 entries: [
-                    { find: "@shared", replacement: "../../shared/lib/src" },
+                    { find: "@shared", replacement: path.join(__dirname, "../shared/lib/src") },
                 ],
             }),
-            commonjs(),
             typescript(),
+            commonjs({
+                include: ["../shared/lib/**"],
+            }),
             json(),
         ],
         external: Object.keys(pkg.dependencies).concat(
             require("module").builtinModules || Object.keys(process.binding("natives")), // eslint-disable-line global-require
-        ),
+        ).concat("winston"),
 
         preserveEntrySignatures: "strict",
         onwarn,
@@ -129,3 +132,5 @@ export default {
         onwarn,
     },
 };
+
+console.log(path.join(__dirname, "../shared/lib/src"));

+ 4 - 3
web/src/routes/auth.ts

@@ -1,6 +1,7 @@
 import { Request as ExpressRequest, Response as ExpressResponse } from "express";
-import { OAuth2 } from "src/util";
-import { ENVIRONMENT } from "src/environment";
+import { OAuth2 } from "src/utils/util";
+import { ENVIRONMENT } from "src/utils/environment";
+import { logger } from "src/utils/logging";
 
 interface CodeResponse {
     code?: string;
@@ -21,7 +22,7 @@ export const get = async (req: ExpressRequest, res: ExpressResponse): Promise<vo
         redirect_uri: ENVIRONMENT.redirectUrl,
     });
     if (result.ok) {
-        console.log(result.access_token);
+        logger.info("Got result: %s", result);
     }
     res.redirect("/");
 };

+ 0 - 9
web/src/routes/example.ts

@@ -1,9 +0,0 @@
-import { Request as ExpressRequest, Response as ExpressResponse } from "express";
-
-export const get = async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
-    res.end("you made a get request");
-};
-
-export const post = async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
-    res.end("you made a post request");
-};

+ 2 - 3
web/src/routes/login.ts

@@ -1,7 +1,6 @@
 import { Request as ExpressRequest, Response as ExpressResponse } from "express";
-import { stringify } from "querystring";
-import { OAuth2 } from "src/util";
-import { ENVIRONMENT } from "src/environment";
+import { OAuth2 } from "src/utils/util";
+import { ENVIRONMENT } from "src/utils/environment";
 
 export const get = async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
     res.redirect(OAuth2.getAuthUrl({

+ 5 - 2
web/src/server.ts

@@ -1,16 +1,19 @@
 // @ts-ignore -- generated package
-import "./environment";
+import "src/utils/environment";
 import * as sapper from "@sapper/server"; // eslint-disable-line import/no-unresolved
 import compression from "compression";
 import express, { Express } from "express";
 // @ts-ignore -- doesn't package its own types until 1.0.0-next.6
 import sirv from "sirv";
+import { logger } from "./utils/logging";
 
 const PORT = process.env.PORT; // eslint-disable-line prefer-destructuring
 // @ts-ignore -- creates a warning after `rollup-plugin-replace` (set up in `rollup.config.js`)
 // replaces `process.env.NODE_ENV` with `"production"` during `prod`
 const dev = process.env.NODE_ENV === "development";
 
+logger.info("Staring webserver");
+
 const createSapperServer = async (): Promise<Express> => {
     const app = express();
 
@@ -25,7 +28,7 @@ const createSapperServer = async (): Promise<Express> => {
 
 createSapperServer().then((app) => {
 	app.listen(PORT, (err?: any): void => { // eslint-disable-line
-        if (err) console.log("error", err);
+        if (err) logger.error("Error on webserver: %s", err);
     });
 });
 

web/src/environment.ts → web/src/utils/environment.ts


+ 12 - 0
web/src/utils/logging.ts

@@ -0,0 +1,12 @@
+import { createLogger } from "@shared/common/logging";
+import { isHttpError } from "@shared/common/async_utils";
+import { HTTPError } from "got/dist/source";
+
+export const logger = createLogger({
+    errorHandler: (reason) => {
+        if (isHttpError<HTTPError>(reason)) {
+            return new Error(`HTTPError: ${reason.request.requestUrl} failed because ${reason}\nStack trace: ${reason.stack}`);
+        }
+        return undefined;
+    },
+});

web/src/rpc.ts → web/src/utils/rpc.ts


+ 1 - 0
web/src/util.ts

@@ -40,6 +40,7 @@ export class OAuth2 {
     static async getToken(opts: AccessTokenRequest):
                         Promise<Option<AccessTokenResponse, { error: string }>> {
         const result = await tryDo(got<AccessTokenResponse>(`${OAUTH_API}/token`, {
+            responseType: "json",
             method: "post",
             form: opts,
         }));

+ 3 - 1
web/tsconfig.json

@@ -5,7 +5,9 @@
             "WebWorker",
             "ES2015"
         ],
-        "target": "ES2016",
+        "module": "ES2015",
+        "esModuleInterop": true,
+        "target": "ES6",
         "allowSyntheticDefaultImports": true,
         "experimentalDecorators": true,
         "emitDecoratorMetadata": true,