import { type ILogger, LogLevel } from "@sapphire/framework"; import pino, { type Logger as PinoInstance } from "pino"; import config from "@/config"; export class PinoSapphireLogger implements ILogger { private pino: PinoInstance; private minLevel: LogLevel; constructor(options?: pino.LoggerOptions, minLevel: LogLevel = LogLevel.Info) { this.minLevel = minLevel; this.pino = pino({ transport: config.isProduction ? undefined : { target: "pino-pretty", options: { colorize: true } }, base: { pid: false, }, level: Bun.env.LOG_LEVEL ?? (config.isProduction ? "info" : "debug"), ...options, }); } public has(level: LogLevel): boolean { return level >= this.minLevel && level !== LogLevel.None; } public write(level: LogLevel, ...values: readonly unknown[]): void { if (!this.has(level)) return; const { name, msg, obj } = this.extractModuleAndPayload(values); const payload = { name, ...obj }; switch (level) { case LogLevel.Trace: this.pino.trace(payload, msg); break; case LogLevel.Debug: this.pino.debug(payload, msg); break; case LogLevel.Info: this.pino.info(payload, msg); break; case LogLevel.Warn: this.pino.warn(payload, msg); break; case LogLevel.Error: this.pino.error(payload, msg); break; case LogLevel.Fatal: this.pino.fatal(payload, msg); break; default: break; } } public trace(...values: readonly unknown[]): void { this.write(LogLevel.Trace, ...values); } public debug(...values: readonly unknown[]): void { this.write(LogLevel.Debug, ...values); } public info(...values: readonly unknown[]): void { this.write(LogLevel.Info, ...values); } public warn(...values: readonly unknown[]): void { this.write(LogLevel.Warn, ...values); } public error(...values: readonly unknown[]): void { this.write(LogLevel.Error, ...values); } public fatal(...values: readonly unknown[]): void { this.write(LogLevel.Fatal, ...values); } private extractModuleAndPayload(values: readonly unknown[]): { name: string; msg: string; obj?: Record; } { let name = "sapphire"; let msg = ""; let err: Error | undefined = undefined; const extra: unknown[] = []; const args = [...values]; if (args.length > 0 && typeof args[0] === "string") { const firstArg = args[0]; const colonIndex = firstArg.indexOf(":"); if (colonIndex !== -1) { name = firstArg.substring(0, colonIndex).trim(); const remainingMessage = firstArg.substring(colonIndex + 1).trim(); if (remainingMessage) args[0] = remainingMessage; else args.shift(); } } for (const val of args) { if (val instanceof Error) err = val; else if (typeof val === "object" && val !== null) extra.push(val); else msg += (msg ? " " : "") + String(val); } const obj: Record = {}; if (err) obj.err = err; if (extra.length > 0) obj.extra = extra; return { name, msg, obj }; } }