| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- import path from "path"
- import fs from "fs/promises"
- import { Global } from "../global"
- import z from "zod"
- export namespace Log {
- export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).openapi({ ref: "LogLevel", description: "Log level" })
- export type Level = z.infer<typeof Level>
- const levelPriority: Record<Level, number> = {
- DEBUG: 0,
- INFO: 1,
- WARN: 2,
- ERROR: 3,
- }
- let level: Level = "INFO"
- function shouldLog(input: Level): boolean {
- return levelPriority[input] >= levelPriority[level]
- }
- export type Logger = {
- debug(message?: any, extra?: Record<string, any>): void
- info(message?: any, extra?: Record<string, any>): void
- error(message?: any, extra?: Record<string, any>): void
- warn(message?: any, extra?: Record<string, any>): void
- tag(key: string, value: string): Logger
- clone(): Logger
- time(
- message: string,
- extra?: Record<string, any>,
- ): {
- stop(): void
- [Symbol.dispose](): void
- }
- }
- const loggers = new Map<string, Logger>()
- export const Default = create({ service: "default" })
- export interface Options {
- print: boolean
- dev?: boolean
- level?: Level
- }
- let logpath = ""
- export function file() {
- return logpath
- }
- export async function init(options: Options) {
- if (options.level) level = options.level
- cleanup(Global.Path.log)
- if (options.print) return
- logpath = path.join(
- Global.Path.log,
- options.dev ? "dev.log" : 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 glob = new Bun.Glob("????-??-??T??????.log")
- const files = await Array.fromAsync(
- glob.scan({
- cwd: dir,
- absolute: true,
- }),
- )
- if (files.length <= 5) return
- const filesToDelete = files.slice(0, -10)
- await Promise.all(filesToDelete.map((file) => fs.unlink(file).catch(() => {})))
- }
- function formatError(error: Error, depth = 0): string {
- const result = error.message
- return error.cause instanceof Error && depth < 10
- ? result + " Caused by: " + formatError(error.cause, depth + 1)
- : result
- }
- let last = Date.now()
- export function create(tags?: Record<string, any>) {
- tags = tags || {}
- const service = tags["service"]
- if (service && typeof service === "string") {
- const cached = loggers.get(service)
- if (cached) {
- return cached
- }
- }
- function build(message: any, extra?: Record<string, any>) {
- const prefix = Object.entries({
- ...tags,
- ...extra,
- })
- .filter(([_, value]) => value !== undefined && value !== null)
- .map(([key, value]) => {
- const prefix = `${key}=`
- if (value instanceof Error) return prefix + formatError(value)
- if (typeof value === "object") return prefix + JSON.stringify(value)
- return prefix + 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: Logger = {
- debug(message?: any, extra?: Record<string, any>) {
- if (shouldLog("DEBUG")) {
- process.stderr.write("DEBUG " + build(message, extra))
- }
- },
- info(message?: any, extra?: Record<string, any>) {
- if (shouldLog("INFO")) {
- process.stderr.write("INFO " + build(message, extra))
- }
- },
- error(message?: any, extra?: Record<string, any>) {
- if (shouldLog("ERROR")) {
- process.stderr.write("ERROR " + build(message, extra))
- }
- },
- warn(message?: any, extra?: Record<string, any>) {
- if (shouldLog("WARN")) {
- 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<string, any>) {
- 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()
- },
- }
- },
- }
- if (service && typeof service === "string") {
- loggers.set(service, result)
- }
- return result
- }
- }
|