server/packages/log-manager/index.ts

192 lines
5.9 KiB
TypeScript
Raw Normal View History

2024-04-07 12:18:21 +02:00
import { appendFile, mkdir, exists } from "node:fs/promises";
2024-04-07 10:56:15 +02:00
import { dirname } from "node:path";
2024-03-09 02:25:17 +01:00
import type { BunFile } from "bun";
export enum LogLevel {
2024-04-07 07:30:49 +02:00
DEBUG = "debug",
INFO = "info",
WARNING = "warning",
ERROR = "error",
CRITICAL = "critical",
2024-03-09 02:25:17 +01:00
}
/**
* Class for handling logging to disk or to stdout
* @param output BunFile of output (can be a normal file or something like Bun.stdout)
*/
export class LogManager {
2024-04-07 07:30:49 +02:00
constructor(private output: BunFile) {
void this.write(
`--- INIT LogManager at ${new Date().toISOString()} ---`,
);
}
/**
* Logs a message to the output
* @param level Importance of the log
* @param entity Emitter of the log
* @param message Message to log
* @param showTimestamp Whether to show the timestamp in the log
*/
async log(
level: LogLevel,
entity: string,
message: string,
showTimestamp = true,
) {
await this.write(
`${
showTimestamp ? `${new Date().toISOString()} ` : ""
}[${level.toUpperCase()}] ${entity}: ${message}`,
);
}
private async write(text: string) {
2024-04-07 12:18:21 +02:00
Bun.stdout.name;
2024-04-07 07:30:49 +02:00
if (this.output === Bun.stdout) {
await Bun.write(Bun.stdout, `${text}\n`);
} else {
2024-04-07 10:56:15 +02:00
if (!(await exists(this.output.name ?? ""))) {
2024-04-07 07:30:49 +02:00
// Create file if it doesn't exist
2024-04-07 10:56:15 +02:00
try {
await mkdir(dirname(this.output.name ?? ""), {
recursive: true,
});
this.output = Bun.file(this.output.name ?? "");
} catch {
//
}
2024-04-07 07:30:49 +02:00
}
await appendFile(this.output.name ?? "", `${text}\n`);
}
}
/**
* Logs an error to the output, wrapper for log
* @param level Importance of the log
* @param entity Emitter of the log
* @param error Error to log
*/
async logError(level: LogLevel, entity: string, error: Error) {
await this.log(level, entity, error.message);
}
/**
* Logs a request to the output
* @param req Request to log
* @param ip IP of the request
* @param logAllDetails Whether to log all details of the request
*/
async logRequest(req: Request, ip?: string, logAllDetails = false) {
let string = ip ? `${ip}: ` : "";
string += `${req.method} ${req.url}`;
if (logAllDetails) {
string += "\n";
string += " [Headers]\n";
// Pretty print headers
for (const [key, value] of req.headers.entries()) {
string += ` ${key}: ${value}\n`;
}
// Pretty print body
string += " [Body]\n";
const content_type = req.headers.get("Content-Type");
if (content_type?.includes("application/json")) {
2024-04-07 14:30:45 +02:00
try {
2024-04-10 08:51:09 +02:00
const json = await req.clone().json();
2024-04-07 14:30:45 +02:00
const stringified = JSON.stringify(json, null, 4)
.split("\n")
.map((line) => ` ${line}`)
.join("\n");
2024-04-07 07:30:49 +02:00
2024-04-07 14:30:45 +02:00
string += `${stringified}\n`;
} catch {
2024-04-10 09:04:46 +02:00
string += ` [Invalid JSON] (raw: ${await req
.clone()
.text()})\n`;
2024-04-07 14:30:45 +02:00
}
2024-04-07 07:30:49 +02:00
} else if (
content_type &&
(content_type.includes("application/x-www-form-urlencoded") ||
content_type.includes("multipart/form-data"))
) {
2024-04-07 14:39:27 +02:00
const formData = await req.clone().formData();
2024-04-07 07:30:49 +02:00
for (const [key, value] of formData.entries()) {
if (value.toString().length < 300) {
string += ` ${key}: ${value.toString()}\n`;
} else {
string += ` ${key}: <${
value.toString().length
} bytes>\n`;
}
}
} else {
const text = await req.text();
string += ` ${text}\n`;
}
}
await this.log(LogLevel.INFO, "Request", string);
}
}
/**
* Outputs to multiple LogManager instances at once
*/
export class MultiLogManager {
2024-04-07 07:30:49 +02:00
constructor(private logManagers: LogManager[]) {}
/**
* Logs a message to all logManagers
* @param level Importance of the log
* @param entity Emitter of the log
* @param message Message to log
* @param showTimestamp Whether to show the timestamp in the log
*/
async log(
level: LogLevel,
entity: string,
message: string,
showTimestamp = true,
) {
for (const logManager of this.logManagers) {
await logManager.log(level, entity, message, showTimestamp);
}
}
/**
* Logs an error to all logManagers
* @param level Importance of the log
* @param entity Emitter of the log
* @param error Error to log
*/
async logError(level: LogLevel, entity: string, error: Error) {
for (const logManager of this.logManagers) {
await logManager.logError(level, entity, error);
}
}
/**
* Logs a request to all logManagers
* @param req Request to log
* @param ip IP of the request
* @param logAllDetails Whether to log all details of the request
*/
async logRequest(req: Request, ip?: string, logAllDetails = false) {
for (const logManager of this.logManagers) {
await logManager.logRequest(req, ip, logAllDetails);
}
}
/**
* Create a MultiLogManager from multiple LogManager instances
* @param logManagers LogManager instances to use
* @returns
*/
static fromLogManagers(...logManagers: LogManager[]) {
return new MultiLogManager(logManagers);
}
2024-03-09 02:25:17 +01:00
}