diff --git a/server/package.json b/server/package.json index f5e21e85e..8ff6a08ce 100644 --- a/server/package.json +++ b/server/package.json @@ -51,6 +51,7 @@ "@types/bull": "^3.15.5", "@types/bunyan": "^1.8.7", "@types/deep-equal": "^1.0.1", + "@valuabletouch/winston-seq": "^1.2.0", "bcryptjs": "^2.4.3", "bull": "^4.1.0", "bunyan": "^1.8.15", @@ -79,6 +80,7 @@ "redis": "3.1.2", "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", + "seq-logging": "^1.1.1", "tachi-common": "0.2.42", "ts-node": "10.0.0", "typescript": "4.3.4", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index a33041a2a..7fbf904a1 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -24,6 +24,7 @@ specifiers: '@types/uuid': 8.3.0 '@typescript-eslint/eslint-plugin': 4.28.0 '@typescript-eslint/parser': 4.28.0 + '@valuabletouch/winston-seq': ^1.2.0 bcryptjs: ^2.4.3 bull: ^4.1.0 bunyan: ^1.8.15 @@ -59,6 +60,7 @@ specifiers: redis: 3.1.2 rimraf: 3.0.2 safe-json-stringify: 1.2.0 + seq-logging: ^1.1.1 supertest: 6.1.3 tachi-common: 0.2.42 tap: 15.0.9 @@ -74,6 +76,7 @@ dependencies: '@types/bull': 3.15.5 '@types/bunyan': 1.8.7 '@types/deep-equal': 1.0.1 + '@valuabletouch/winston-seq': 1.2.0 bcryptjs: 2.4.3 bull: 4.1.0 bunyan: 1.8.15 @@ -102,6 +105,7 @@ dependencies: redis: 3.1.2 rimraf: 3.0.2 safe-json-stringify: 1.2.0 + seq-logging: 1.1.1 tachi-common: 0.2.42 ts-node: 10.0.0_83f53b0a0c5616d3fa00ed4e30b9ce1b typescript: 4.3.4 @@ -834,6 +838,13 @@ packages: eslint-visitor-keys: 2.1.0 dev: true + /@valuabletouch/winston-seq/1.2.0: + resolution: {integrity: sha512-w29tdQQBcGk2Wp+LNGVnW36OYxyW1cRToCWFehOxshhEcqZFdJvWbnvgiN5aU1VRY7CIV8P0MEMKBHyQq3jhTg==} + dependencies: + seq-logging: 1.1.1 + winston-transport: 4.4.0 + dev: false + /accepts/1.3.7: resolution: {integrity: sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==} engines: {node: '>= 0.6'} @@ -3847,6 +3858,10 @@ packages: statuses: 1.5.0 dev: false + /seq-logging/1.1.1: + resolution: {integrity: sha512-9miWILWu22dKNCkZi2UePAnZeQEzaYsQRKbAi5eSBUbuOyyeYPyYO1bEvIvZAZRjltEwY1S0yz94hc0/f+niDg==} + dev: false + /serve-static/1.14.1: resolution: {integrity: sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==} engines: {node: '>= 0.8.0'} diff --git a/server/src/lib/logger/logger.ts b/server/src/lib/logger/logger.ts index 8b0425183..e938963ec 100644 --- a/server/src/lib/logger/logger.ts +++ b/server/src/lib/logger/logger.ts @@ -1,9 +1,11 @@ -import winston, { format, transports, Logger, LeveledLogMethod } from "winston"; -import { EscapeStringRegexp } from "utils/misc"; -import "winston-daily-rotate-file"; -import SafeJSONStringify from "safe-json-stringify"; +import { Transport as SeqTransport } from "@valuabletouch/winston-seq"; import { Environment, ServerConfig } from "lib/setup/config"; -import CreateDiscordWinstonTransport from "./discord-transport"; +import SafeJSONStringify from "safe-json-stringify"; +import { SeqLogLevel } from "seq-logging"; +import { EscapeStringRegexp } from "utils/misc"; +import winston, { format, LeveledLogMethod, Logger, transports } from "winston"; +import "winston-daily-rotate-file"; +import DiscordWinstonTransport from "./discord-transport"; export type KtLogger = Logger & { severe: LeveledLogMethod }; @@ -116,18 +118,22 @@ const consoleFormatRoute = format.combine( }) ); -const tports: winston.transport[] = [ - new transports.DailyRotateFile({ - filename: "logs/tachi-%DATE%.log", - datePattern: "YYYY-MM-DD-HH", - zippedArchive: true, - maxSize: "20m", - maxFiles: "14d", - createSymlink: true, - symlinkName: "tachi.log", - format: defaultFormatRoute, - }), -]; +const tports: winston.transport[] = []; + +if (ServerConfig.LOGGER_CONFIG.FILE) { + tports.push( + new transports.DailyRotateFile({ + filename: "logs/tachi-%DATE%.log", + datePattern: "YYYY-MM-DD-HH", + zippedArchive: true, + maxSize: "20m", + maxFiles: "14d", + createSymlink: true, + symlinkName: "tachi.log", + format: defaultFormatRoute, + }) + ); +} if (ServerConfig.LOGGER_CONFIG.CONSOLE) { tports.push( @@ -139,13 +145,37 @@ if (ServerConfig.LOGGER_CONFIG.CONSOLE) { if (ServerConfig.LOGGER_CONFIG.DISCORD) { tports.push( - new CreateDiscordWinstonTransport({ + new DiscordWinstonTransport({ webhook: ServerConfig.LOGGER_CONFIG.DISCORD.WEBHOOK_URL, level: "warn", }) ); } +if (ServerConfig.LOGGER_CONFIG.SEQ_API_KEY && Environment.seqUrl) { + // Turns winston log levels into seq format. + const levelMap: Record = { + crit: "Fatal", + severe: "Error", + error: "Error", + warn: "Warning", + info: "Information", + // Very cool that they've decided to swap these. Very funny. + verbose: "Debug", + debug: "Verbose", + }; + + tports.push( + new SeqTransport({ + apiKey: ServerConfig.LOGGER_CONFIG.SEQ_API_KEY, + serverUrl: Environment.seqUrl, + levelMapper(level = "") { + return levelMap[level] ?? "information"; + }, + }) + ); +} + export const rootLogger = winston.createLogger({ levels: { crit: 0, // entire process termination is necessary @@ -161,6 +191,19 @@ export const rootLogger = winston.createLogger({ transports: tports, }); +if (!!ServerConfig.LOGGER_CONFIG.SEQ_API_KEY !== !!Environment.seqUrl) { + rootLogger.warn( + `Only one of SEQ_API_KEY (conf.json5) and SEQ_URL (Environment) were set. Not sending logs to Seq, as both must be provided.` + ); +} + +if (tports.length === 0) { + // eslint-disable-next-line no-console + console.warn( + "You have no transports set. Absolutely no logs will be saved. This is a terrible idea!" + ); +} + function CreateLogCtx(filename: string, lg = rootLogger): KtLogger { const replacedFilename = filename.replace( new RegExp(`^${EscapeStringRegexp(process.cwd())}/((js|src)/)?`, "u"),