Ver Fonte

refactor: Continued implementing logging class

Owen Diffey há 1 ano atrás
pai
commit
6be255044c
3 ficheiros alterados com 184 adições e 114 exclusões
  1. 1 0
      backend/logs/.gitignore
  2. 165 91
      backend/src/LogBook.ts
  3. 18 23
      backend/src/main.ts

+ 1 - 0
backend/logs/.gitignore

@@ -0,0 +1 @@
+*

+ 165 - 91
backend/src/LogBook.ts

@@ -1,3 +1,5 @@
+import fs from "fs";
+
 export type Log = {
 	timestamp: number;
 	message: string;
@@ -6,41 +8,66 @@ export type Log = {
 	data?: Record<string, any>;
 };
 
+export type LogFilters = {
+	include: Partial<Omit<Log, "timestamp">>[];
+	exclude: Partial<Omit<Log, "timestamp">>[];
+};
+
+export type LogOutputOptions = Record<
+	"timestamp" | "title" | "type" | "message" | "data" | "color",
+	boolean
+> &
+	Partial<LogFilters>;
+
+export type LogOutputs = {
+	console: LogOutputOptions;
+	file: LogOutputOptions;
+	memory: { enabled: boolean } & Partial<LogFilters>;
+};
+
 export default class LogBook {
 	private logs: Log[];
 
-	private filter: {
-		include: Partial<Omit<Log, "timestamp">>[];
-		exclude: Partial<Omit<Log, "timestamp">>[];
-		silence: Partial<Omit<Log, "timestamp">>[];
-	};
+	private outputs: LogOutputs;
 
-	private display: Record<
-		"timestamp" | "title" | "type" | "message" | "data",
-		boolean
-	>;
+	private stream: fs.WriteStream;
 
 	/**
 	 * Log Book
 	 */
-	public constructor() {
+	public constructor(file = "logs/backend.log") {
 		this.logs = [];
-		this.filter = {
-			include: [],
-			exclude: [],
-			silence: [
-				{
-					category: "jobs"
-				}
-			]
-		};
-		this.display = {
-			timestamp: true,
-			title: true,
-			type: false,
-			message: true,
-			data: false
+		this.outputs = {
+			console: {
+				timestamp: true,
+				title: true,
+				type: false,
+				message: true,
+				data: false,
+				color: true,
+				exclude: [
+					{
+						category: "jobs",
+						type: "success"
+					},
+					{
+						type: "debug"
+					}
+				]
+			},
+			file: {
+				timestamp: true,
+				title: true,
+				type: true,
+				message: true,
+				data: false,
+				color: false
+			},
+			memory: {
+				enabled: false
+			}
 		};
+		this.stream = fs.createWriteStream(file, { flags: "a" });
 	}
 
 	/**
@@ -53,46 +80,57 @@ export default class LogBook {
 			timestamp: Date.now(),
 			...(typeof log === "string" ? { message: log } : log)
 		};
-		let exclude = false;
-		let silence = false;
+		const exclude = {
+			console: false,
+			file: false,
+			memory: false
+		};
 		Object.entries(logObject).forEach(([key, value]) => {
-			if (
-				(this.filter.include.length > 0 &&
+			Object.entries(this.outputs).forEach(([outputName, output]) => {
+				if (
+					(output.include &&
+						output.include.length > 0 &&
+						output.include.filter(
+							// @ts-ignore
+							filter => filter[key] === value
+						).length === 0) ||
+					(output.exclude &&
+						output.exclude.filter(
+							// @ts-ignore
+							filter => filter[key] === value
+						).length > 0)
+				)
 					// @ts-ignore
-					this.filter.include.filter(filter => filter[key] === value)
-						.length === 0) ||
-				// @ts-ignore
-				this.filter.exclude.filter(filter => filter[key] === value)
-					.length > 0
-			)
-				exclude = true;
-			if (
-				// @ts-ignore
-				this.filter.silence.filter(filter => filter[key] === value)
-					.length > 0
-			)
-				silence = true;
+					exclude[outputName] = true;
+			});
 		});
-		if (!exclude) {
-			this.logs.push(logObject); // TODO: Replace with file storage
-			if (!silence) {
-				this.printMessage(
-					logObject,
-					(logObject.data && logObject.data.jobName) ||
-						logObject.category ||
-						undefined
-				);
-			}
-		}
+		const title =
+			(logObject.data && logObject.data.jobName) ||
+			logObject.category ||
+			undefined;
+		if (!exclude.memory && this.outputs.memory.enabled)
+			this.logs.push(logObject);
+		if (!exclude.console)
+			console.log(this.formatMessage(logObject, title, "console"));
+		if (!exclude.file)
+			this.stream.write(
+				`${this.formatMessage(logObject, title, "file")}\n`
+			);
 	}
 
 	/**
-	 * printMessage - Output formatted log to stdout
+	 * formatMessage - Format log to string
 	 *
 	 * @param log - Log
 	 * @param title - Log title
+	 * @param destination - Message destination
+	 * @returns Formatted log string
 	 */
-	private printMessage(log: Log, title?: string) {
+	private formatMessage(
+		log: Log,
+		title: string | undefined,
+		destination: "console" | "file"
+	): string {
 		const centerString = (string: string, length: number) => {
 			const spaces = Array(
 				Math.floor((length - Math.max(0, string.length)) / 2)
@@ -102,54 +140,90 @@ export default class LogBook {
 			} `;
 		};
 		let message = "";
-		switch (log.type) {
-			case "success":
-				message += "\x1b[32m";
-				break;
-			case "error":
-				message += "\x1b[31m";
-				break;
-			case "debug":
-				message += "\x1b[33m";
-				break;
-			case "info":
-			default:
-				message += "\x1b[36m";
-				break;
-		}
-		if (this.display.timestamp) message += `| ${log.timestamp} `;
-		if (this.display.title)
+		if (this.outputs[destination].color)
+			switch (log.type) {
+				case "success":
+					message += "\x1b[32m";
+					break;
+				case "error":
+					message += "\x1b[31m";
+					break;
+				case "debug":
+					message += "\x1b[33m";
+					break;
+				case "info":
+				default:
+					message += "\x1b[36m";
+					break;
+			}
+		if (this.outputs[destination].timestamp)
+			message += `| ${log.timestamp} `;
+		if (this.outputs[destination].title)
 			message += centerString(title ? title.substring(0, 20) : "", 24);
-		if (this.display.type)
+		if (this.outputs[destination].type)
 			message += centerString(
 				log.type ? log.type.toUpperCase() : "INFO",
 				10
 			);
-		if (this.display.message) message += `| ${log.message} `;
-		if (this.display.data) message += `| ${JSON.stringify(log.data)} `;
-		message += "\x1b[0m";
-		console.log(message);
+		if (this.outputs[destination].message) message += `| ${log.message} `;
+		if (this.outputs[destination].data)
+			message += `| ${JSON.stringify(log.data)} `;
+		if (this.outputs[destination].color) message += "\x1b[0m";
+		return message;
 	}
 
 	/**
-	 * setFilter - Apply filters for current session
+	 * updateOutput - Update output settings
 	 *
-	 * @param filter - Filter type
-	 * @param action - Action
-	 * @param filters - Filters
+	 * @param output - Output name
+	 * @param key - Output key to update
+	 * @param action - Update action
+	 * @param values - Updated value
 	 */
-	public setFilter<T extends keyof LogBook["filter"]>(
-		filter: T,
+	public async updateOutput(
+		output: "console" | "file" | "memory",
+		key: keyof LogOutputOptions | "enabled",
 		action: "set" | "add" | "reset",
-		filters?: LogBook["filter"][T]
+		values?: any
 	) {
-		if (action === "reset") this.filter[filter] = [];
-		if (action === "set" || action === "add") {
-			if (!filters || filters.length === 0)
-				throw new Error("No filters provided");
-			if (action === "set") this.filter[filter] = filters;
-			if (action === "add")
-				this.filter[filter] = [...this.filter[filter], ...filters];
+		switch (key) {
+			case "include":
+			case "exclude": {
+				if (action === "set" || action === "add") {
+					if (!values) throw new Error("No filters provided");
+					const filters = Array.isArray(values) ? values : [values];
+					if (action === "set") this.outputs[output][key] = filters;
+					if (action === "add")
+						this.outputs[output][key] = [
+							...(this.outputs[output][key] || []),
+							...filters
+						];
+				} else if (action === "reset") {
+					this.outputs[output][key] = [];
+				} else
+					throw new Error(
+						`Action "${action}" not found for ${key} in ${output}`
+					);
+				break;
+			}
+			case "enabled": {
+				if (output === "memory" && action === "set")
+					this.outputs[output][key] = values;
+				else
+					throw new Error(
+						`Action "${action}" not found for ${key} in ${output}`
+					);
+				break;
+			}
+			default: {
+				if (output !== "memory" && action === "set") {
+					if (!values) throw new Error("No value provided");
+					this.outputs[output][key] = values;
+				} else
+					throw new Error(
+						`Action "${action}" not found for ${key} in ${output}`
+					);
+			}
 		}
 	}
 }

+ 18 - 23
backend/src/main.ts

@@ -90,34 +90,29 @@ const runCommand = (line: string) => {
 			break;
 		}
 		case "log": {
-			const [filter, action, ...filters] = args;
-			if (!filter) {
-				console.log(`Missing filter type`);
-				break;
-			}
+			const [output, key, action, ...values] = args;
 			if (
-				!(
-					filter === "silence" ||
-					filter === "include" ||
-					filter === "exclude"
-				)
+				output === undefined ||
+				key === undefined ||
+				action === undefined
 			) {
-				console.log(`Invalid filter type "${filter}"`);
-				break;
-			}
-			if (!(action === "set" || action === "add" || action === "reset")) {
-				console.log(`Invalid filter action "${action}"`);
+				console.log(
+					`Missing required parameters (log <output> <key> <action> [values])`
+				);
 				break;
 			}
-			if (filters.length === 0 && action !== "reset") {
-				console.log(`No filters defined for "${filter}"`);
-				break;
+			let value: any[] | undefined;
+			if (values !== undefined && values.length >= 1) {
+				value = values.map(_filter => JSON.parse(_filter));
+				if (value.length === 1) [value] = value;
 			}
-			logBook.setFilter(
-				filter,
-				action,
-				filters.map(_filter => JSON.parse(_filter))
-			);
+			logBook
+				// @ts-ignore
+				.updateOutput(output, key, action, value)
+				.then(() => console.log("Successfully updated outputs"))
+				.catch((err: Error) =>
+					console.log(`Error updating outputs "${err.message}"`)
+				);
 			break;
 		}
 		default: {