Browse Source

feat: Started adding logging class

Owen Diffey 2 years ago
parent
commit
d4b9325f82

+ 20 - 5
backend/src/BaseModule.ts

@@ -20,7 +20,7 @@ export default abstract class BaseModule {
 		this.moduleManager = moduleManager;
 		this.name = name;
 		this.status = "LOADED";
-		// console.log(`Module (${this.name}) starting`);
+		this.log(`Module (${this.name}) loaded`);
 	}
 
 	/**
@@ -55,7 +55,7 @@ export default abstract class BaseModule {
 	 */
 	public startup(): Promise<void> {
 		return new Promise(resolve => {
-			console.log(`Module (${this.name}) starting`);
+			this.log(`Module (${this.name}) starting`);
 			this.setStatus("STARTING");
 			resolve();
 		});
@@ -65,7 +65,7 @@ export default abstract class BaseModule {
 	 * started - called with the module has started
 	 */
 	protected started(): void {
-		console.log(`Module (${this.name}) started`);
+		this.log(`Module (${this.name}) started`);
 		this.setStatus("STARTED");
 	}
 
@@ -74,7 +74,7 @@ export default abstract class BaseModule {
 	 */
 	public shutdown(): Promise<void> {
 		return new Promise(resolve => {
-			console.log(`Module (${this.name}) stopping`);
+			this.log(`Module (${this.name}) stopping`);
 			this.setStatus("STOPPING");
 			this.stopped();
 			resolve();
@@ -85,7 +85,22 @@ export default abstract class BaseModule {
 	 * stopped - called when the module has stopped
 	 */
 	protected stopped(): void {
-		console.log(`Module (${this.name}) stopped`);
+		this.log(`Module (${this.name}) stopped`);
 		this.setStatus("STOPPED");
 	}
+
+	/**
+	 * log - Add log to logbook
+	 *
+	 * @param message - Log message
+	 */
+	protected log(message: string) {
+		this.moduleManager.logBook.log({
+			message,
+			category: `modules`,
+			data: {
+				moduleName: this.name
+			}
+		});
+	}
 }

+ 7 - 3
backend/src/Job.ts

@@ -5,7 +5,11 @@ export default class Job {
 
 	protected module: string;
 
-	protected callback: (resolve: () => void, reject: () => void) => void;
+	protected callback: (
+		job: this,
+		resolve: () => void,
+		reject: () => void
+	) => void;
 
 	protected priority: number;
 
@@ -35,7 +39,7 @@ export default class Job {
 	public constructor(
 		name: string,
 		module: string,
-		callback: (resolve: () => void, reject: () => void) => void,
+		callback: (job: Job, resolve: () => void, reject: () => void) => void,
 		options?: { priority: number; longJob?: string }
 	) {
 		this.name = name;
@@ -118,7 +122,7 @@ export default class Job {
 	public execute(): Promise<void> {
 		return new Promise((resolve, reject) => {
 			this.setStatus("ACTIVE");
-			this.callback(resolve, reject);
+			this.callback(this, resolve, reject);
 		});
 	}
 }

+ 14 - 6
backend/src/JobQueue.ts

@@ -130,12 +130,20 @@ export default class JobQueue {
 	public getStats() {
 		return {
 			...this.stats,
-			total: Object.values(this.stats).reduce((a, b) => ({
-				successful: a.successful + b.successful,
-				failed: a.failed + b.failed,
-				total: a.total + b.total,
-				added: a.added + b.added
-			}))
+			total: Object.values(this.stats).reduce(
+				(a, b) => ({
+					successful: a.successful + b.successful,
+					failed: a.failed + b.failed,
+					total: a.total + b.total,
+					added: a.added + b.added
+				}),
+				{
+					successful: 0,
+					failed: 0,
+					total: 0,
+					added: 0
+				}
+			)
 		};
 	}
 

+ 155 - 0
backend/src/LogBook.ts

@@ -0,0 +1,155 @@
+export type Log = {
+	timestamp: number;
+	message: string;
+	type?: "info" | "success" | "error" | "debug";
+	category?: string;
+	data?: Record<string, any>;
+};
+
+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 display: Record<
+		"timestamp" | "title" | "type" | "message" | "data",
+		boolean
+	>;
+
+	/**
+	 * Log Book
+	 */
+	public constructor() {
+		this.logs = [];
+		this.filter = {
+			include: [],
+			exclude: [],
+			silence: [
+				{
+					category: "jobs"
+				}
+			]
+		};
+		this.display = {
+			timestamp: true,
+			title: true,
+			type: false,
+			message: true,
+			data: false
+		};
+	}
+
+	/**
+	 * log - Add log
+	 *
+	 * @param log - Log message or object
+	 */
+	public log(log: string | Omit<Log, "timestamp">) {
+		const logObject: Log = {
+			timestamp: Date.now(),
+			...(typeof log === "string" ? { message: log } : log)
+		};
+		let exclude = false;
+		let silence = false;
+		Object.entries(logObject).forEach(([key, value]) => {
+			if (
+				(this.filter.include.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;
+		});
+		if (!exclude) {
+			this.logs.push(logObject); // TODO: Replace with file storage
+			if (!silence) {
+				this.printMessage(
+					logObject,
+					(logObject.data && logObject.data.jobName) ||
+						logObject.category ||
+						undefined
+				);
+			}
+		}
+	}
+
+	/**
+	 * printMessage - Output formatted log to stdout
+	 *
+	 * @param log - Log
+	 * @param title - Log title
+	 */
+	private printMessage(log: Log, title?: string) {
+		const centerString = (string: string, length: number) => {
+			const spaces = Array(
+				Math.floor((length - Math.max(0, string.length)) / 2)
+			).join(" ");
+			return `| ${spaces}${string}${spaces}${
+				string.length % 2 === 0 ? "" : " "
+			} `;
+		};
+		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)
+			message += centerString(title ? title.substring(0, 20) : "", 24);
+		if (this.display.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);
+	}
+
+	/**
+	 * setFilter - Apply filters for current session
+	 *
+	 * @param filter - Filter type
+	 * @param action - Action
+	 * @param filters - Filters
+	 */
+	public setFilter<T extends keyof LogBook["filter"]>(
+		filter: T,
+		action: "set" | "add" | "reset",
+		filters?: LogBook["filter"][T]
+	) {
+		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];
+		}
+	}
+}

+ 26 - 2
backend/src/ModuleManager.ts

@@ -2,18 +2,23 @@ import async from "async";
 import BaseModule from "./BaseModule";
 import Job from "./Job";
 import JobQueue from "./JobQueue";
+import LogBook from "./LogBook";
 import { Jobs, Modules, ModuleStatus, ModuleClass } from "./types/Modules";
 
 export default class ModuleManager {
 	private modules?: Modules;
 
+	public logBook: LogBook;
+
 	private jobQueue: JobQueue;
 
 	/**
 	 * Module Manager
 	 *
+	 * @param logBook - Logbook
 	 */
-	public constructor() {
+	public constructor(logBook: LogBook) {
+		this.logBook = logBook;
 		this.jobQueue = new JobQueue();
 	}
 
@@ -202,14 +207,33 @@ export default class ModuleManager {
 						new Job(
 							jobName.toString(),
 							moduleName,
-							(resolveJob, rejectJob) => {
+							(job, resolveJob, rejectJob) => {
 								jobFunction
 									.apply(module, [payload])
 									.then((response: R) => {
+										this.logBook.log({
+											message:
+												"Job completed successfully",
+											type: "success",
+											category: "jobs",
+											data: {
+												jobName: job.getName(),
+												jobId: job.getUuid()
+											}
+										});
 										resolveJob();
 										resolve(response);
 									})
 									.catch((err: any) => {
+										this.logBook.log({
+											message: `Job failed with error "${err}"`,
+											type: "error",
+											category: "jobs",
+											data: {
+												jobName: job.getName(),
+												jobId: job.getUuid()
+											}
+										});
 										rejectJob();
 										reject(err);
 									});

+ 34 - 16
backend/src/main.ts

@@ -1,22 +1,9 @@
-import util from "util";
 import * as readline from "node:readline";
 import ModuleManager from "./ModuleManager";
+import LogBook from "./LogBook";
 
-// Replace console.log with something that replaced certain phrases/words
-const blacklistedConsoleLogs: string[] = [];
-const oldConsole = { log: console.log };
-console.log = (...args) => {
-	const string = util.format.apply(null, args);
-	let blacklisted = false;
-
-	blacklistedConsoleLogs.forEach(blacklistedConsoleLog => {
-		if (string.indexOf(blacklistedConsoleLog) !== -1) blacklisted = true;
-	});
-
-	if (!blacklisted) oldConsole.log.apply(null, args);
-};
-
-const moduleManager = new ModuleManager();
+const logBook = new LogBook();
+const moduleManager = new ModuleManager(logBook);
 moduleManager.startup();
 
 // TOOD remove, or put behind debug option
@@ -102,6 +89,37 @@ const runCommand = (line: string) => {
 			debugger;
 			break;
 		}
+		case "log": {
+			const [filter, action, ...filters] = args;
+			if (!filter) {
+				console.log(`Missing filter type`);
+				break;
+			}
+			if (
+				!(
+					filter === "silence" ||
+					filter === "include" ||
+					filter === "exclude"
+				)
+			) {
+				console.log(`Invalid filter type "${filter}"`);
+				break;
+			}
+			if (!(action === "set" || action === "add" || action === "reset")) {
+				console.log(`Invalid filter action "${action}"`);
+				break;
+			}
+			if (filters.length === 0 && action !== "reset") {
+				console.log(`No filters defined for "${filter}"`);
+				break;
+			}
+			logBook.setFilter(
+				filter,
+				action,
+				filters.map(_filter => JSON.parse(_filter))
+			);
+			break;
+		}
 		default: {
 			if (!/^\s*$/.test(command))
 				console.log(`Command "${command}" not found`);

+ 0 - 1
backend/src/modules/OtherModule.ts

@@ -20,7 +20,6 @@ export default class OtherModule extends BaseModule {
 			super
 				.startup()
 				.then(() => {
-					console.log("Other Startup");
 					super.started();
 					resolve();
 				})

+ 1 - 1
backend/src/modules/StationModule.ts

@@ -20,7 +20,7 @@ export default class StationModule extends BaseModule {
 			super
 				.startup()
 				.then(() => {
-					console.log("Station Startup");
+					this.log("Station Startup");
 					super.started();
 					resolve();
 				})