commit 4b00caf7d4cd2ac1a651f8bf5f934a9b817f1970 Author: Strix Date: Mon Feb 3 11:33:47 2025 +0100 init diff --git a/Dispatch.ts b/Dispatch.ts new file mode 100644 index 0000000..2f2a1bd --- /dev/null +++ b/Dispatch.ts @@ -0,0 +1,45 @@ +import { statSync } from "node:fs"; +import { IDispatch, IDispatchConfig } from "./IDispatch"; +import { ILogItem } from "./ILogItem"; +import { LogLevel } from "./LogLevel"; + +export class Dispatch implements IDispatch { + private dispatches: IDispatch[] = []; + + constructor(config: IDispatchConfig = {}) {} + + trace(...args: any[]): void { + this.process({ timestamp: new Date(), level: LogLevel.Trace, data: args }); + } + + debug(...args: any[]): void { + this.process({ timestamp: new Date(), level: LogLevel.Debug, data: args }); + } + + info(...args: any[]): void { + this.process({ timestamp: new Date(), level: LogLevel.Info, data: args }); + } + + warn(...args: any[]): void { + this.process({ timestamp: new Date(), level: LogLevel.Warn, data: args }); + } + + error(...args: any[]): void { + this.process({ timestamp: new Date(), level: LogLevel.Error, data: args }); + } + + fatal(...args: any[]): void { + this.process({ timestamp: new Date(), level: LogLevel.Fatal, data: args }); + } + + process(item: ILogItem): void { + for (const dispatch of this.dispatches) { + dispatch.process(item); + } + } + + add(dispatch: IDispatch): Dispatch { + this.dispatches.push(dispatch); + return this; + } +} diff --git a/FileDispatch.ts b/FileDispatch.ts new file mode 100644 index 0000000..a094780 --- /dev/null +++ b/FileDispatch.ts @@ -0,0 +1,24 @@ +import * as fs from "fs"; +import { FormattedDispatch } from "./FormattedDispatch"; +import { App } from "../App"; +import { ILogItem } from "./ILogItem"; + +export class FileDispatch extends FormattedDispatch { + path: string; + stream: fs.WriteStream; + + constructor(path: string) { + super(); + this.path = path; + this.stream = fs.createWriteStream(path, { flags: "a" }); + + this.stream.on("error", (err) => { + App.logger.error(`Error writing to file: ${this.path}`, err); + }); + } + + process(item: ILogItem): void { + this.stream.write(this.format(item) + "\n"); + super.process(item); + } +} diff --git a/FormattedDispatch.ts b/FormattedDispatch.ts new file mode 100644 index 0000000..10e424a --- /dev/null +++ b/FormattedDispatch.ts @@ -0,0 +1,37 @@ +import { Dispatch } from "./Dispatch"; +import { ILogItem } from "./ILogItem"; +import { LogLevel } from "./LogLevel"; + +export class FormattedDispatch extends Dispatch { + private formatString: string = "%t %l <%o>: %m"; + + format(item: ILogItem): string { + return this.formatString + .replace("%t", item.timestamp.toISOString()) + .replace("%l", LogLevel[item.level]) + .replace("%o", item.origin || "") + .replace( + "%m", + item.data + .map((item: any) => { + switch (typeof item) { + case "string": + case "number": + return item; + case "object": + return JSON.stringify(item); + default: + return item; + } + }) + .join(" "), + ); + } + + process(item: ILogItem): void { + super.process({ + ...item, + data: this.format(item), + }); + } +} diff --git a/IDispatch.ts b/IDispatch.ts new file mode 100644 index 0000000..15e6601 --- /dev/null +++ b/IDispatch.ts @@ -0,0 +1,7 @@ +import { ILogItem } from "./ILogItem"; + +export interface IDispatchConfig {} + +export interface IDispatch { + process(item: ILogItem): void; +} diff --git a/ILogItem.ts b/ILogItem.ts new file mode 100644 index 0000000..5fc0103 --- /dev/null +++ b/ILogItem.ts @@ -0,0 +1,8 @@ +import { LogLevel } from "./LogLevel"; + +export interface ILogItem { + timestamp: Date; + origin?: string; + level: LogLevel; + data: any; +} diff --git a/LogLevel.ts b/LogLevel.ts new file mode 100644 index 0000000..78f2f98 --- /dev/null +++ b/LogLevel.ts @@ -0,0 +1,8 @@ +export enum LogLevel { + Trace, + Debug, + Info, + Warn, + Error, + Fatal +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a50df7 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# s410 logger + +Dispatch based logging, created by Didier a.k.a. s410 or s41uc0. \ No newline at end of file diff --git a/SmartDispatch.ts b/SmartDispatch.ts new file mode 100644 index 0000000..420efbc --- /dev/null +++ b/SmartDispatch.ts @@ -0,0 +1,44 @@ +import { statSync } from "fs"; +import { Dispatch } from "./Dispatch"; +import { ILogItem } from "./ILogItem"; + +export class SmartDispatch extends Dispatch { + process(item: ILogItem) { + let err = new Error(); + // we need to look through the stack and keep track of intermediate dispatches + let stack = err.stack!.split("\n"); + let i = 0; + while (!stack[i].includes("at SmartDispatch.process")) i++; + + let src = stack[i + 2].trim(); + let path = null; + + // now we need to format the src (at ...:..:.. or at App. ..., etc) + if (src.includes("at ")) src = src.split("at ")[1]; + if (src.includes(" (")) src = src.split(" (")[1]; + if (src.includes(")")) src = src.split(")")[0]; + + let file = { + path: src.split(":")[0], + line: src.split(":")[1], + column: src.split(":")[2], + }; + + try { + path = statSync(file.path).isFile() ? file.path : null; + } catch (e) { + path = null; + } + + if (path) { + // trim the path to the project root + if (path.startsWith(process.cwd())) + path = "." + path.slice(process.cwd().length); + src = `${path}:${file.line}:${file.column}`; + } + super.process({ + ...item, + origin: src, + }); + } +} diff --git a/StdoutDispatch.ts b/StdoutDispatch.ts new file mode 100644 index 0000000..ddce5b1 --- /dev/null +++ b/StdoutDispatch.ts @@ -0,0 +1,11 @@ +import { FormattedDispatch } from "./FormattedDispatch"; +import { IDispatch } from "./IDispatch"; +import { ILogItem } from "./ILogItem"; +import { LogLevel } from "./LogLevel"; + +export class StdoutDispatch extends FormattedDispatch { + process(item: ILogItem) { + process.stdout.write(this.format(item) + "\n"); + super.process(item); + } +}