frost-api-wrapper
Version:
A wrapper that allows you to use the websocket and API from G-Portal.
133 lines (112 loc) • 3.89 kB
text/typescript
import { LogLevel, RCEEvent } from "../constants";
import { inspect } from "util";
import { type LoggerOptions } from "../types";
import fs from "fs";
import path from "path";
import { v4 as uuidv4 } from "uuid";
import RCEManager from "./RCEManager";
enum ConsoleColor {
Reset = "\x1b[0m",
FgRed = "\x1b[31m",
FgGreen = "\x1b[32m",
FgYellow = "\x1b[33m",
FgCyan = "\x1b[36m",
FgMagenta = "\x1b[35m",
}
interface LogType {
prefix: string;
emoji: string;
color: string;
}
export default class Logger {
private emitter: RCEManager;
private level: LogLevel;
private file: string | undefined;
private static LOG_TYPES: Record<string, LogType> = {
warn: { prefix: "[WARNING]", emoji: "⚠️", color: ConsoleColor.FgYellow },
info: { prefix: "[INFO]", emoji: "💬", color: ConsoleColor.FgCyan },
debug: { prefix: "[DEBUG]", emoji: "🔧", color: ConsoleColor.FgMagenta },
error: { prefix: "[ERROR]", emoji: "❌", color: ConsoleColor.FgRed },
};
public constructor(emitter: RCEManager, opts: LoggerOptions) {
this.level = opts.logLevel ?? LogLevel.Info;
this.file = opts.logFile;
this.emitter = emitter;
if (this.file) {
this.validateFilePath();
}
}
private validateFilePath(): void {
if (this.file && !fs.existsSync(this.file)) {
const dir = path.dirname(this.file);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(this.file, "");
}
}
private handleFileSize(): void {
if (this.file) {
const stats = fs.statSync(this.file);
if (stats.size > 300_000_000) {
const archiveName = `${this.file}.${uuidv4()}.log`;
fs.renameSync(this.file, archiveName);
fs.writeFileSync(this.file, "");
}
}
}
private async logToFileAsync(type: string, content: any): Promise<void> {
if (this.file) {
const stats = await fs.promises.stat(this.file).catch(() => null);
if (stats && stats.size > 300_000_000) {
const archiveName = `${this.file}.${uuidv4()}.log`;
await fs.promises.rename(this.file, archiveName);
await fs.promises.writeFile(this.file, "");
}
const logMessage =
typeof content === "string"
? `[${type.toUpperCase()}]: ${content}\n`
: `[${type.toUpperCase()}]: ${inspect(content, { depth: 5 })}\n`;
await fs.promises.appendFile(this.file, logMessage);
}
}
private format(content: any): string {
return typeof content === "string"
? content
: inspect(content, { depth: 5 });
}
private formatTimestamp(date: Date): string {
return date.toLocaleTimeString([], { hour12: true });
}
private log(
level: LogLevel,
type: string,
content: any,
logType: LogType
): void {
this.handleFileSize();
this.logToFileAsync(type, content).catch(console.error);
if (this.level !== LogLevel.None && level <= this.level) {
const timestamp = this.formatTimestamp(new Date());
const formattedMessage = `\x1b[90m[${timestamp}]\x1b[0m ${logType.color}${logType.prefix}${ConsoleColor.Reset} ${logType.emoji} ${this.format(content)}`;
console.log(formattedMessage);
this.emitter.emit(RCEEvent.Log, { level, content: this.format(content) });
}
}
public logMessage(level: LogLevel, type: keyof typeof Logger.LOG_TYPES, content: any): void {
const logType = Logger.LOG_TYPES[type];
this.log(level, type, content, logType);
}
public warn(content: any): void {
this.logMessage(LogLevel.Warn, "warn", content);
}
public info(content: any): void {
this.logMessage(LogLevel.Info, "info", content);
}
public debug(content: any): void {
this.logMessage(LogLevel.Debug, "debug", content);
}
public error(content: any): void {
this.logMessage(LogLevel.Error, "error", content);
}
}