import path from "path" import fs from "fs/promises" import { Global } from "../global" export namespace Log { export const Default = create({ service: "default" }) export interface Options { print: boolean } let logpath = "" export function file() { return logpath } export async function init(options: Options) { const dir = path.join(Global.Path.data, "log") await fs.mkdir(dir, { recursive: true }) cleanup(dir) if (options.print) return logpath = path.join( dir, new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log", ) const logfile = Bun.file(logpath) await fs.truncate(logpath).catch(() => {}) const writer = logfile.writer() process.stderr.write = (msg) => { writer.write(msg) writer.flush() return true } } async function cleanup(dir: string) { const entries = await fs.readdir(dir, { withFileTypes: true }) const files = entries .filter((entry) => entry.isFile() && entry.name.endsWith(".log")) .map((entry) => path.join(dir, entry.name)) if (files.length <= 5) return const filesToDelete = files.slice(0, -10) await Promise.all( filesToDelete.map((file) => fs.unlink(file).catch(() => {})), ) } let last = Date.now() export function create(tags?: Record) { tags = tags || {} function build(message: any, extra?: Record) { const prefix = Object.entries({ ...tags, ...extra, }) .filter(([_, value]) => value !== undefined && value !== null) .map(([key, value]) => `${key}=${value}`) .join(" ") const next = new Date() const diff = next.getTime() - last last = next.getTime() return ( [next.toISOString().split(".")[0], "+" + diff + "ms", prefix, message] .filter(Boolean) .join(" ") + "\n" ) } const result = { info(message?: any, extra?: Record) { process.stderr.write("INFO " + build(message, extra)) }, error(message?: any, extra?: Record) { process.stderr.write("ERROR " + build(message, extra)) }, warn(message?: any, extra?: Record) { process.stderr.write("WARN " + build(message, extra)) }, tag(key: string, value: string) { if (tags) tags[key] = value return result }, clone() { return Log.create({ ...tags }) }, time(message: string, extra?: Record) { const now = Date.now() result.info(message, { status: "started", ...extra }) function stop() { result.info(message, { status: "completed", duration: Date.now() - now, ...extra, }) } return { stop, [Symbol.dispose]() { stop() }, } }, } return result } }