From 4b00caf7d4cd2ac1a651f8bf5f934a9b817f1970 Mon Sep 17 00:00:00 2001 From: Strix Date: Mon, 3 Feb 2025 11:33:47 +0100 Subject: [PATCH] init --- Dispatch.ts | 45 ++++++++++++++++++++++++++++++++++++++++++++ FileDispatch.ts | 24 +++++++++++++++++++++++ FormattedDispatch.ts | 37 ++++++++++++++++++++++++++++++++++++ IDispatch.ts | 7 +++++++ ILogItem.ts | 8 ++++++++ LogLevel.ts | 8 ++++++++ README.md | 3 +++ SmartDispatch.ts | 44 +++++++++++++++++++++++++++++++++++++++++++ StdoutDispatch.ts | 11 +++++++++++ 9 files changed, 187 insertions(+) create mode 100644 Dispatch.ts create mode 100644 FileDispatch.ts create mode 100644 FormattedDispatch.ts create mode 100644 IDispatch.ts create mode 100644 ILogItem.ts create mode 100644 LogLevel.ts create mode 100644 README.md create mode 100644 SmartDispatch.ts create mode 100644 StdoutDispatch.ts 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); + } +}