Prechádzať zdrojové kódy

Start work on event logger

ghorsington 4 rokov pred
rodič
commit
00ca0daf45

+ 5 - 1
.env.template

@@ -19,4 +19,8 @@ RPC_PORT=
 WEB_COOKIE_KEY=
 WEB_AUTH_URI=
 HCAPTCHA_SITEKEY=
-HCAPTCHA_SECRET=
+HCAPTCHA_SECRET=
+
+MONGO_DB_HOST=
+MONGO_DB_USERNAME=
+MONGO_DB_PASSWORD=

+ 5 - 4
Makefile

@@ -1,5 +1,6 @@
 ifeq (dev, $(ENV))
     dc = docker-compose -f docker-compose.yml -f docker-compose.dev.yml
+	helpers = mongo-express
 else
     dc = docker-compose
 endif
@@ -20,16 +21,16 @@ build:
 	$(dc) build
 
 start_env: build
-	$(dc) up db adminer facedetect
+	$(dc) up db mongo adminer facedetect $(helpers)
 
 start: build
-	$(dc) up db adminer noctbot web facedetect
+	$(dc) up db mongo adminer noctbot web facedetect $(helpers)
 
 start_bot: build
-	$(dc) up db adminer noctbot facedetect
+	$(dc) up db mongo adminer noctbot facedetect $(helpers)
 
 start_web: build
-	$(dc) up db adminer web
+	$(dc) up db mongo adminer web $(helpers)
 
 npmi:
 	cd shared && npm install

+ 86 - 0
bot/package-lock.json

@@ -1336,6 +1336,15 @@
          "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
          "dev": true
       },
+      "bl": {
+         "version": "2.2.1",
+         "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz",
+         "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==",
+         "requires": {
+            "readable-stream": "^2.3.5",
+            "safe-buffer": "^5.1.1"
+         }
+      },
       "bmp-js": {
          "version": "0.1.0",
          "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
@@ -1500,6 +1509,11 @@
             "pako": "~1.0.5"
          }
       },
+      "bson": {
+         "version": "1.1.5",
+         "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz",
+         "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg=="
+      },
       "buffer": {
          "version": "5.6.0",
          "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
@@ -2263,6 +2277,11 @@
          "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
          "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
       },
+      "denque": {
+         "version": "1.4.1",
+         "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
+         "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
+      },
       "depd": {
          "version": "1.1.2",
          "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -4488,6 +4507,12 @@
          "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
          "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
       },
+      "memory-pager": {
+         "version": "1.5.0",
+         "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+         "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+         "optional": true
+      },
       "merge-descriptors": {
          "version": "1.0.1",
          "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@@ -4612,6 +4637,19 @@
             "moment": ">= 2.9.0"
          }
       },
+      "mongodb": {
+         "version": "3.6.2",
+         "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.2.tgz",
+         "integrity": "sha512-sSZOb04w3HcnrrXC82NEh/YGCmBuRgR+C1hZgmmv4L6dBz4BkRse6Y8/q/neXer9i95fKUBbFi4KgeceXmbsOA==",
+         "requires": {
+            "bl": "^2.2.1",
+            "bson": "^1.1.4",
+            "denque": "^1.4.1",
+            "require_optional": "^1.0.1",
+            "safe-buffer": "^5.1.2",
+            "saslprep": "^1.0.0"
+         }
+      },
       "ms": {
          "version": "2.1.2",
          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -5658,6 +5696,27 @@
          "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
          "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
       },
+      "require_optional": {
+         "version": "1.0.1",
+         "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
+         "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
+         "requires": {
+            "resolve-from": "^2.0.0",
+            "semver": "^5.1.0"
+         },
+         "dependencies": {
+            "resolve-from": {
+               "version": "2.0.0",
+               "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
+               "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
+            },
+            "semver": {
+               "version": "5.7.1",
+               "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+               "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+            }
+         }
+      },
       "resolve-alpn": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz",
@@ -5754,6 +5813,15 @@
          "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
          "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
       },
+      "saslprep": {
+         "version": "1.0.3",
+         "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
+         "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
+         "optional": true,
+         "requires": {
+            "sparse-bitfield": "^3.0.3"
+         }
+      },
       "sax": {
          "version": "1.2.4",
          "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
@@ -6098,6 +6166,15 @@
          "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
          "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM="
       },
+      "sparse-bitfield": {
+         "version": "3.0.3",
+         "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+         "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
+         "optional": true,
+         "requires": {
+            "memory-pager": "^1.0.2"
+         }
+      },
       "split": {
          "version": "1.0.1",
          "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
@@ -7020,6 +7097,15 @@
             }
          }
       },
+      "winston-mongodb": {
+         "version": "5.0.5",
+         "resolved": "https://registry.npmjs.org/winston-mongodb/-/winston-mongodb-5.0.5.tgz",
+         "integrity": "sha512-hUb5DStkzdLjJ7h4+ZoBbL/NJsEphy/uY9mw2F5of4iAI1Bx1INrAFzlKZx+Ww0w9IOevrg2cPKSGlDawn6SNQ==",
+         "requires": {
+            "mongodb": "^3.6.2",
+            "winston-transport": "^4.4.0"
+         }
+      },
       "winston-transport": {
          "version": "4.4.0",
          "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz",

+ 1 - 0
bot/package.json

@@ -70,6 +70,7 @@
       "typescript-rest-rpc": "^1.0.10",
       "uws": "^100.0.1",
       "winston": "^3.3.3",
+      "winston-mongodb": "^5.0.5",
       "winston-transport": "^4.4.0",
       "yaml": "^1.10.0"
    },

+ 1 - 0
bot/src/environment.ts

@@ -8,6 +8,7 @@ if (process.env.NODE_ENV == "dev") {
         path: "../db.env"
     });
     
+    process.env.MONGO_DB_HOST = "localhost";
     process.env.TYPEORM_HOST = "localhost";
     process.env.FACEDETECT_URL = "localhost:8081";
     process.env.TYPEORM_USERNAME = process.env.DB_USERNAME;

+ 8 - 1
bot/src/logging.ts

@@ -1,4 +1,4 @@
-import { createLogger } from "@shared/common/logging";
+import { createEventLogger, createLogger } from "@shared/common/logging";
 import { isHttpError } from "@shared/common/async_utils";
 import { HTTPError } from "got/dist/source";
 
@@ -9,4 +9,11 @@ export const logger = createLogger({
         }
         return undefined;
     }
+});
+
+export const eventLogger = createEventLogger({
+    host: process.env.MONGO_DB_HOST ?? "",
+    password: process.env.MONGO_DB_USERNAME ?? "",
+    username: process.env.MONGO_DB_PASSWORD ?? "",
+    name: "NoctBot",
 });

+ 2 - 1
bot/src/main.ts

@@ -7,7 +7,7 @@ import { client } from "./client";
 import { createConnection, getConnectionOptions } from "typeorm";
 import { DB_ENTITIES } from "@shared/db/entities";
 import { assertOk } from "@shared/common/async_utils";
-import { logger } from "./logging";
+import { eventLogger, logger } from "./logging";
 import { PluginManager } from "./plugin_manager";
 import { startRpcServer } from "./rpc";
 
@@ -16,6 +16,7 @@ export const COMMAND_PREFIX = "/";
 
 client.bot.on("ready", async () => {
     logger.info("Starting up NoctBot");
+    eventLogger.info("Started");
     await client.botUser.setActivity(`@${client.botUser.username} help`, { type: "PLAYING" });
     await assertOk(plgMgr.start(client.bot));
     logger.info("NoctBot is ready");

+ 4 - 1
bot/src/plugins/greet.ts

@@ -1,7 +1,7 @@
 import { Plugin, BotEventData, Event } from "src/model/plugin";
 import { GuildMember, PartialGuildMember, TextChannel } from "discord.js";
 import { getRepository } from "typeorm";
-import { logger } from "src/logging";
+import { eventLogger, logger } from "src/logging";
 import { GuildGreeting } from "@shared/db/entity/GuildGreeting";
 import { client } from "src/client";
 import { formatString } from "src/util";
@@ -12,6 +12,8 @@ import { tryDo } from "@shared/common/async_utils";
 export class GreetPlugin {
     @Event("guildMemberAdd")
     async showGreetMessage(data: BotEventData, member: GuildMember | PartialGuildMember): Promise<void> {
+        eventLogger.info("User %s#%s (%s) joined server %s (%s)", member.user?.username, member.user?.discriminator, member.user?.id, member.guild.name, member.guild.id);
+
         const greetInfo = await this.displayGreet(member, g => g.onJoinMessage);
         if (!greetInfo || !greetInfo.onJoinPrivateMessage) {
             return;
@@ -41,6 +43,7 @@ export class GreetPlugin {
 
     @Event("guildMemberRemove")
     async showGoodbyeMessage(data: BotEventData, member: GuildMember | PartialGuildMember): Promise<void> {
+        eventLogger.info("User %s#%s (%s) left server %s (%s)", member.user?.username, member.user?.discriminator, member.user?.id, member.guild.name, member.guild.id);
         await this.displayGreet(member, g => g.onLeaveMessage);
     }
 

+ 3 - 1
bot/src/rpc.ts

@@ -3,7 +3,7 @@ import { createServer } from "http";
 import { ModuleRpcServer } from "rpc_ts/lib/server";
 import { ModuleRpcProtocolServer } from "rpc_ts/lib/protocol/server";
 import { NoctBotService } from "@shared/rpc/backend";
-import { logger } from "./logging";
+import { eventLogger, logger } from "./logging";
 import { client } from "./client";
 import { tryDo } from "@shared/common/async_utils";
 import { getRepository } from "typeorm";
@@ -59,8 +59,10 @@ const handler: ModuleRpcServer.ServiceHandlerFor<typeof NoctBotService> = {
         return { ok: !(await checkUser("verify", userId, async (user, guild) => {
             const result = await tryDo(user.roles.add(guild.verifiedRoleId));
             if (result.ok) {
+                eventLogger.info("Verifying user %s#%s (%s)", user.user.username, user.user.discriminator, user.user.id);
                 logger.info("Verifying user %s#%s (%s) on guild %s", user.user.username, user.user.discriminator, user.user.id, guild.guildId);
             } else {
+                eventLogger.warn("Failed to verify user %s#%s (%s): %s", user.user.username, user.user.discriminator, user.user.id, result.error);
                 logger.warn("Failed to verify user %s#%s (%s) on guild %s: %s", user.user.username, user.user.discriminator, user.user.id, guild.guildId, result.error);
             }
             return !result.ok;

+ 14 - 1
docker-compose.dev.yml

@@ -11,4 +11,17 @@ services:
 
   facedetect:
     ports:
-      - 8081:80
+      - 8081:80
+
+  mongo:
+    ports:
+      - "27017:27017"
+  
+  mongo-express:
+    image: mongo-express
+    restart: unless-stopped
+    ports:
+      - 3040:8081
+    environment:
+      ME_CONFIG_MONGODB_ADMINUSERNAME: ${MONGO_DB_USERNAME}
+      ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_DB_PASSWORD}

+ 12 - 0
docker-compose.yml

@@ -9,6 +9,7 @@ services:
     restart: unless-stopped
     depends_on:
       - db
+      - mongo
       - facedetect
     env_file:
       - .env
@@ -42,6 +43,7 @@ services:
     restart: unless-stopped
     depends_on:
       - db
+      - mongo
       - noctbot
     env_file:
       - .env
@@ -75,8 +77,18 @@ services:
     ports:
       - 3030:8080
 
+  mongo:
+    image: mongo
+    restart: unless-stopped
+    environment: 
+      MONGO_INITDB_ROOT_USERNAME: ${MONGO_DB_USERNAME}
+      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_DB_PASSWORD}
+    volumes: 
+      - mongo-data:/data/db
+
 volumes:
   db-data:
+  mongo-data:
   web-data:
   caddy-data:
   caddy-config:

+ 122 - 0
shared/package-lock.json

@@ -79,6 +79,53 @@
       "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
       "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
     },
+    "bl": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz",
+      "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==",
+      "requires": {
+        "readable-stream": "^2.3.5",
+        "safe-buffer": "^5.1.1"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          },
+          "dependencies": {
+            "safe-buffer": {
+              "version": "5.1.2",
+              "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+              "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+            }
+          }
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          },
+          "dependencies": {
+            "safe-buffer": {
+              "version": "5.1.2",
+              "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+              "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+            }
+          }
+        }
+      }
+    },
     "brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -88,6 +135,11 @@
         "concat-map": "0.0.1"
       }
     },
+    "bson": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz",
+      "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg=="
+    },
     "buffer": {
       "version": "5.6.0",
       "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
@@ -267,6 +319,11 @@
       "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
       "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
     },
+    "denque": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
+      "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
+    },
     "dotenv": {
       "version": "8.2.0",
       "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
@@ -442,6 +499,12 @@
         "triple-beam": "^1.3.0"
       }
     },
+    "memory-pager": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+      "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+      "optional": true
+    },
     "minimatch": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@@ -455,6 +518,19 @@
       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
       "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
     },
+    "mongodb": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.2.tgz",
+      "integrity": "sha512-sSZOb04w3HcnrrXC82NEh/YGCmBuRgR+C1hZgmmv4L6dBz4BkRse6Y8/q/neXer9i95fKUBbFi4KgeceXmbsOA==",
+      "requires": {
+        "bl": "^2.2.1",
+        "bson": "^1.1.4",
+        "denque": "^1.4.1",
+        "require_optional": "^1.0.1",
+        "safe-buffer": "^5.1.2",
+        "saslprep": "^1.0.0"
+      }
+    },
     "ms": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -575,6 +651,20 @@
       "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
       "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
     },
+    "require_optional": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
+      "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
+      "requires": {
+        "resolve-from": "^2.0.0",
+        "semver": "^5.1.0"
+      }
+    },
+    "resolve-from": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
+      "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
+    },
     "rimraf": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -588,11 +678,25 @@
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
       "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
     },
+    "saslprep": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
+      "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
+      "optional": true,
+      "requires": {
+        "sparse-bitfield": "^3.0.3"
+      }
+    },
     "sax": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
       "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
     },
+    "semver": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+    },
     "set-blocking": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -615,6 +719,15 @@
         "is-arrayish": "^0.3.1"
       }
     },
+    "sparse-bitfield": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+      "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
+      "optional": true,
+      "requires": {
+        "memory-pager": "^1.0.2"
+      }
+    },
     "sprintf-js": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -743,6 +856,15 @@
         "winston-transport": "^4.4.0"
       }
     },
+    "winston-mongodb": {
+      "version": "5.0.5",
+      "resolved": "https://registry.npmjs.org/winston-mongodb/-/winston-mongodb-5.0.5.tgz",
+      "integrity": "sha512-hUb5DStkzdLjJ7h4+ZoBbL/NJsEphy/uY9mw2F5of4iAI1Bx1INrAFzlKZx+Ww0w9IOevrg2cPKSGlDawn6SNQ==",
+      "requires": {
+        "mongodb": "^3.6.2",
+        "winston-transport": "^4.4.0"
+      }
+    },
     "winston-transport": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz",

+ 1 - 0
shared/package.json

@@ -16,6 +16,7 @@
     "typeorm": "^0.2.26",
     "typescript": "^3.9.3",
     "winston": "^3.3.3",
+    "winston-mongodb": "^5.0.5",
     "winston-transport": "^4.4.0"
   },
   "devDependencies": {

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

@@ -1,3 +1,4 @@
+import * as winston_mongo from "winston-mongodb";
 import * as winston from "winston";
 import * as nodemailer from "nodemailer";
 import TransportStream from "winston-transport";
@@ -5,6 +6,31 @@ import Mail from "nodemailer/lib/mailer";
 import { hasStackTrace } from "./async_utils";
 import { inspect } from "util";
 
+export function createEventLogger(opts: {
+    host: string,
+    username: string,
+    password: string,
+    name: string,
+}) {
+    return winston.createLogger({
+        level: "debug",
+        format: winston.format.combine(
+            winston.format.splat(),
+            winston.format.prettyPrint(),
+        ),
+        transports: [
+            new winston_mongo.MongoDB({
+                db: `mongodb://${opts.username}:${opts.password}@${opts.host}`,
+                tryReconnect: true,
+                collection: "logs",
+                label: opts.name,
+                level: "debug",
+                leaveConnectionOpen: false,
+            })
+        ]
+    });
+}
+
 export function createLogger(opts?: {
     errorHandler?: (reason: unknown) => Error | undefined
 }) {

+ 113 - 9
web/package-lock.json

@@ -1793,6 +1793,39 @@
 			"resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
 			"integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A=="
 		},
+		"bl": {
+			"version": "2.2.1",
+			"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz",
+			"integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==",
+			"requires": {
+				"readable-stream": "^2.3.5",
+				"safe-buffer": "^5.1.1"
+			},
+			"dependencies": {
+				"readable-stream": {
+					"version": "2.3.7",
+					"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+					"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+					"requires": {
+						"core-util-is": "~1.0.0",
+						"inherits": "~2.0.3",
+						"isarray": "~1.0.0",
+						"process-nextick-args": "~2.0.0",
+						"safe-buffer": "~5.1.1",
+						"string_decoder": "~1.1.1",
+						"util-deprecate": "~1.0.1"
+					}
+				},
+				"string_decoder": {
+					"version": "1.1.1",
+					"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+					"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+					"requires": {
+						"safe-buffer": "~5.1.0"
+					}
+				}
+			}
+		},
 		"body-parser": {
 			"version": "1.19.0",
 			"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
@@ -1851,6 +1884,11 @@
 				"node-releases": "^1.1.60"
 			}
 		},
+		"bson": {
+			"version": "1.1.5",
+			"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz",
+			"integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg=="
+		},
 		"buffer": {
 			"version": "5.6.0",
 			"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
@@ -2606,6 +2644,11 @@
 			"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
 			"dev": true
 		},
+		"denque": {
+			"version": "1.4.1",
+			"resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
+			"integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
+		},
 		"depd": {
 			"version": "1.1.2",
 			"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -3975,6 +4018,12 @@
 			"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
 			"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
 		},
+		"memory-pager": {
+			"version": "1.5.0",
+			"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+			"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+			"optional": true
+		},
 		"merge-descriptors": {
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@@ -4048,6 +4097,19 @@
 			"resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
 			"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
 		},
+		"mongodb": {
+			"version": "3.6.2",
+			"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.2.tgz",
+			"integrity": "sha512-sSZOb04w3HcnrrXC82NEh/YGCmBuRgR+C1hZgmmv4L6dBz4BkRse6Y8/q/neXer9i95fKUBbFi4KgeceXmbsOA==",
+			"requires": {
+				"bl": "^2.2.1",
+				"bson": "^1.1.4",
+				"denque": "^1.4.1",
+				"require_optional": "^1.0.1",
+				"safe-buffer": "^5.1.2",
+				"saslprep": "^1.0.0"
+			}
+		},
 		"ms": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -5338,6 +5400,22 @@
 			"integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
 			"dev": true
 		},
+		"require_optional": {
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
+			"integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
+			"requires": {
+				"resolve-from": "^2.0.0",
+				"semver": "^5.1.0"
+			},
+			"dependencies": {
+				"resolve-from": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
+					"integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
+				}
+			}
+		},
 		"resolve": {
 			"version": "1.17.0",
 			"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
@@ -5452,14 +5530,14 @@
 			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
 		},
 		"sapper": {
-			"version": "0.28.9",
-			"resolved": "https://registry.npmjs.org/sapper/-/sapper-0.28.9.tgz",
-			"integrity": "sha512-ngowemh3h4wCKE+/EYCn0OZWhuavbqYnR9O2ZavFzdlszE5AznDXuQOwEgnX73U8hS4y9kN3kOudErYmKCjdDg==",
+			"version": "0.28.6",
+			"resolved": "https://registry.npmjs.org/sapper/-/sapper-0.28.6.tgz",
+			"integrity": "sha512-PLCZXBcXcL2ISBh+CqoUxcT0MwMnA8N71UDNhQig0Em5Z8vqg+g2jpKfYSnnyxqB5Plwc9LipN5ZJr42Vs4hXg==",
 			"dev": true,
 			"requires": {
 				"html-minifier": "^4.0.0",
 				"http-link-header": "^1.0.2",
-				"shimport": "^2.0.4",
+				"shimport": "^1.0.1",
 				"source-map": "^0.6.1",
 				"sourcemap-codec": "^1.4.6",
 				"string-hash": "^1.1.3"
@@ -5473,6 +5551,15 @@
 				}
 			}
 		},
+		"saslprep": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
+			"integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
+			"optional": true,
+			"requires": {
+				"sparse-bitfield": "^3.0.3"
+			}
+		},
 		"sax": {
 			"version": "1.2.4",
 			"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
@@ -5481,8 +5568,7 @@
 		"semver": {
 			"version": "5.7.1",
 			"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
-			"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
-			"dev": true
+			"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
 		},
 		"send": {
 			"version": "0.17.1",
@@ -5566,9 +5652,9 @@
 			"dev": true
 		},
 		"shimport": {
-			"version": "2.0.4",
-			"resolved": "https://registry.npmjs.org/shimport/-/shimport-2.0.4.tgz",
-			"integrity": "sha512-5YOyQqYkOFSkPFnpS87De6BYzDiZBc8FS4/aTuGZiST+WmXSwWRoaNRHqyVOeEpSx9wlgYWg9WYfCuzD/11/qA==",
+			"version": "1.0.1",
+			"resolved": "https://registry.npmjs.org/shimport/-/shimport-1.0.1.tgz",
+			"integrity": "sha512-Imf4gH+8WQmT1GvxS/x79qpmfnE6m50hyN1ucatX+7oMCgmaF8obZWCPIzSUe6+P+YmXM46lkP2pxiV2/lt9Og==",
 			"dev": true
 		},
 		"showdown": {
@@ -5758,6 +5844,15 @@
 			"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
 			"dev": true
 		},
+		"sparse-bitfield": {
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+			"integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
+			"optional": true,
+			"requires": {
+				"memory-pager": "^1.0.2"
+			}
+		},
 		"spdx-correct": {
 			"version": "3.1.1",
 			"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
@@ -6413,6 +6508,15 @@
 				}
 			}
 		},
+		"winston-mongodb": {
+			"version": "5.0.5",
+			"resolved": "https://registry.npmjs.org/winston-mongodb/-/winston-mongodb-5.0.5.tgz",
+			"integrity": "sha512-hUb5DStkzdLjJ7h4+ZoBbL/NJsEphy/uY9mw2F5of4iAI1Bx1INrAFzlKZx+Ww0w9IOevrg2cPKSGlDawn6SNQ==",
+			"requires": {
+				"mongodb": "^3.6.2",
+				"winston-transport": "^4.4.0"
+			}
+		},
 		"winston-transport": {
 			"version": "4.4.0",
 			"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz",

+ 3 - 1
web/package.json

@@ -31,11 +31,13 @@
 		"typeorm": "^0.2.26",
 		"vanilla-hcaptcha": "0.0.5",
 		"winston": "^3.3.3",
+		"winston-mongodb": "^5.0.5",
 		"winston-transport": "^4.4.0"
 	},
 	"devDependencies": {
 		"@babel/core": "^7.11.6",
 		"@babel/plugin-syntax-dynamic-import": "^7.8.3",
+		"@babel/plugin-transform-async-to-generator": "^7.10.4",
 		"@babel/plugin-transform-runtime": "^7.11.5",
 		"@babel/preset-env": "^7.11.5",
 		"@babel/runtime": "^7.11.2",
@@ -73,7 +75,7 @@
 		"rollup": "^2.28.2",
 		"rollup-plugin-svelte": "^5.2.3",
 		"rollup-plugin-terser": "^7.0.2",
-		"sapper": "^0.28.9",
+		"sapper": "^0.28.6",
 		"svelte": "^3.28.0",
 		"svelte-preprocess": "^4.3.2",
 		"tailwindcss": "^1.8.10",

+ 4 - 7
web/rollup.config.js

@@ -40,7 +40,8 @@ const onwarn = (warning, _onwarn) => (warning.code === "CIRCULAR_DEPENDENCY" &&
 export default {
     client: {
         input: config.client.input().replace(/\.js$/, ".ts"),
-        output: { ...config.client.output() },
+        output: { ...config.client.output(), sourcemap },
+        external: (id) => id.includes("@babel/runtime"),
         plugins: [
             replace({
                 "process.browser": true,
@@ -68,12 +69,8 @@ export default {
             legacy && babel({
                 extensions: [".js", ".mjs", ".html", ".svelte"],
                 babelHelpers: "runtime",
-                exclude: ["node_modules/@babel/**"],
-                presets: [
-                    ["@babel/preset-env", {
-                        targets: "> 0.25%, not dead",
-                    }],
-                ],
+                exclude: ["node_modules/**"],
+                presets: ["@babel/preset-env"],
                 plugins: [
                     "@babel/plugin-syntax-dynamic-import",
                     ["@babel/plugin-transform-runtime", {

+ 14 - 0
web/src/routes/_layout.svelte

@@ -45,6 +45,7 @@
   const ROUTES_NAV = [
     { route: "rules", moderator: false, name: "Rules" },
     { route: "rules/edit", moderator: true, name: "Edit rules" },
+    { route: "logs", moderator: true, name: "Event logs" },
   ];
 </script>
 
@@ -73,6 +74,19 @@
       }
     }
   }
+
+  .viewport {
+    @apply bg-gray-700 h-screen w-full pb-8 px-20 pt-16;
+    margin-top: 3rem;
+    height: calc(100vh - 3rem);
+    overflow-y: auto;
+  }
+
+  @screen lg {
+    .viewport {
+      @apply w-2/4;
+    }
+  }
 </style>
 
 

+ 3 - 1
web/src/routes/login/discord.ts

@@ -1,7 +1,7 @@
 import { Request as ExpressRequest, Response as ExpressResponse } from "express";
 import { DiscordAPI } from "src/utils/util";
 import { Option, tryDo } from "@shared/common/async_utils";
-import { logger } from "src/utils/logging";
+import { eventLogger, logger } from "src/utils/logging";
 import { rpcClient } from "src/utils/rpc";
 import { ENV } from "src/utils/environment";
 
@@ -27,6 +27,7 @@ export const post = async (req: ExpressRequest, res: ExpressResponse):
     }
     if (!req.session.authTokenCode) {
         logger.warn("WEB: attempted to join with no authTokenCode set!");
+        eventLogger.warn("IP %s attempted to join with no authTokenCode set", req.ip);
         return res.json({
             ok: false,
             error: "Authentication token is missing. Please try logging in again.",
@@ -52,6 +53,7 @@ export const post = async (req: ExpressRequest, res: ExpressResponse):
         logger.error("WEB: failed to auth user %s: %s", userResult.id, userInServerResult.error);
         return res.json({ ok: false, error: "Couldn't determine if user joined the server, please try again later" });
     }
+    eventLogger.info("%s#%s authed from %s", userResult.username, userResult.discriminator, req.ip);
     logger.verbose("Logged in as %s#%s (%s); user in server: (%s)", userResult.username, userResult.discriminator, userResult.id, userInServerResult.result.exists);
     if (!userInServerResult.result.exists) {
         return res.json({ ok: false, error: "You haven't joined any servers NoctBot manages! Please join first and try again!" });

+ 2 - 0
web/src/routes/login/discord_callback.ts

@@ -1,4 +1,5 @@
 import { Request as ExpressRequest, Response as ExpressResponse } from "express";
+import { eventLogger } from "src/utils/logging";
 
 interface CodeResponse {
     code?: string;
@@ -9,5 +10,6 @@ export const get = async (req: ExpressRequest, res: ExpressResponse): Promise<vo
     if (req.session) {
         req.session.authTokenCode = data.code;
     }
+    eventLogger.verbose("Discord auth callback: %s", req.ip);
     res.redirect("/login/discord_auth");
 };

+ 63 - 0
web/src/routes/logs.svelte

@@ -0,0 +1,63 @@
+<script lang="typescript">
+    import type { PollOptions, PollResult, LogEvent } from "src/routes/logs/poll";
+
+    let logEvents: LogEvent[] = [];
+
+    async function pollEvents() {
+        const params: PollOptions = {
+            limit: "50",
+        };
+        const query = Object.entries(params).reduce(
+            (prev, [key, val]) =>
+                `${prev}&${encodeURIComponent(key)}=${encodeURIComponent(val)}`,
+            ""
+        );
+        const response = await fetch(`/logs/poll?${query}`, {
+            method: "get",
+            credentials: "include",
+            headers: {
+                Accept: "application/json",
+                "Content-Type": "application/json",
+            },
+        });
+
+        const result = (await response.json()) as PollResult;
+        console.log(result);
+        if (result.ok) {
+            logEvents = result.events;
+        }
+    }
+</script>
+
+<style>
+</style>
+
+<div class="viewport text-white">
+    <h1 class="text-3xl pb-4">Event logs</h1>
+    <form class="py-2" on:submit|preventDefault={pollEvents}>
+        <input
+            class="bg-blue-600 py-2 px-3 text-white cursor-pointer rounded-sm"
+            type="submit"
+            value="Query" />
+    </form>
+    <table class="py-3">
+        <thead>
+            <tr>
+                <th>Source</th>
+                <th>Timestamp</th>
+                <th>Level</th>
+                <th>Message</th>
+            </tr>
+        </thead>
+        <tbody>
+            {#each logEvents as logEvent (logEvent._id)}
+                <tr>
+                    <td>{logEvent.label}</td>
+                    <td>{logEvent.timestamp}</td>
+                    <td>{logEvent.level}</td>
+                    <td>{logEvent.message}</td>
+                </tr>
+            {/each}
+        </tbody>
+    </table>
+</div>

+ 0 - 13
web/src/routes/rules/index.svelte

@@ -81,19 +81,6 @@
 <style>
   @import "./markdown-dark.css";
 
-  .viewport {
-    @apply bg-gray-700 h-screen w-full pb-8 px-20 pt-16;
-    margin-top: 3rem;
-    height: calc(100vh - 3rem);
-    overflow-y: auto;
-  }
-
-  @screen lg {
-    .viewport {
-      @apply w-2/4;
-    }
-  }
-
   .message {
     @apply text-white;
   }

+ 2 - 1
web/src/server.ts

@@ -8,7 +8,7 @@ import session from "cookie-session";
 import sirv from "sirv";
 import { createConnection, getConnectionOptions } from "typeorm";
 import { DB_ENTITIES } from "@shared/db/entities";
-import { logger } from "./utils/logging";
+import { eventLogger, logger } from "./utils/logging";
 import { AppSession } from "./utils/session";
 
 const PORT = process.env.PORT; // eslint-disable-line prefer-destructuring
@@ -17,6 +17,7 @@ const PORT = process.env.PORT; // eslint-disable-line prefer-destructuring
 const dev = process.env.NODE_ENV === "development";
 
 logger.info("Staring webserver to port %s", PORT);
+eventLogger.info("Started");
 
 const key = process.env.WEB_COOKIE_KEY;
 

+ 1 - 0
web/src/utils/environment.ts

@@ -8,6 +8,7 @@ if (process.env.NODE_ENV === "development") {
         path: "../db.env",
     });
 
+    process.env.MONGO_DB_HOST = "localhost";
     process.env.TYPEORM_HOST = "localhost";
     process.env.NOCTBOT_ADDR = "localhost";
     process.env.WEB_DATA_PATH = "./web_data";

+ 30 - 1
web/src/utils/logging.ts

@@ -1,6 +1,26 @@
-import { createLogger } from "@shared/common/logging";
+import { createEventLogger, createLogger } from "@shared/common/logging";
 import { isHttpError } from "@shared/common/async_utils";
 import { HTTPError } from "got/dist/source";
+import { MongoDB } from "winston-mongodb";
+import { QueryOptions } from "winston";
+
+// Workaround for https://github.com/winstonjs/winston-mongodb/issues/168
+MongoDB.prototype.normalizeQuery = (opts?: Record<string, unknown>) : QueryOptions => {
+    const result = opts ?? {};
+    result.rows = result.rows ?? result.limit ?? 10;
+    result.start = result.start ?? 0;
+    result.until = result.until ?? new Date();
+    if (typeof result.until !== "object") {
+        result.until = new Date(result.until as unknown as Date);
+    }
+    result.from = result.from ?? ((result.until as unknown as number) - (24 * 60 * 60 * 1000));
+    if (typeof result.from !== "object") {
+        result.from = new Date(result.from as unknown as Date);
+    }
+    result.order = result.order ?? "desc";
+
+    return result as unknown as QueryOptions;
+};
 
 export const logger = createLogger({
     errorHandler: (reason) => {
@@ -10,3 +30,12 @@ export const logger = createLogger({
         return undefined;
     },
 });
+
+export const LOGGER_NAME = "WEB";
+
+export const eventLogger = createEventLogger({
+    host: process.env.MONGO_DB_HOST ?? "",
+    password: process.env.MONGO_DB_USERNAME ?? "",
+    username: process.env.MONGO_DB_PASSWORD ?? "",
+    name: LOGGER_NAME,
+});