log.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import path from "path"
  2. import fs from "fs/promises"
  3. import { Global } from "../global"
  4. export namespace Log {
  5. export const Default = create({ service: "default" })
  6. export interface Options {
  7. print: boolean
  8. }
  9. let logpath = ""
  10. export function file() {
  11. return logpath
  12. }
  13. export async function init(options: Options) {
  14. const dir = path.join(Global.Path.data, "log")
  15. await fs.mkdir(dir, { recursive: true })
  16. cleanup(dir)
  17. if (options.print) return
  18. logpath = path.join(
  19. dir,
  20. new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log",
  21. )
  22. const logfile = Bun.file(logpath)
  23. await fs.truncate(logpath).catch(() => {})
  24. const writer = logfile.writer()
  25. process.stderr.write = (msg) => {
  26. writer.write(msg)
  27. writer.flush()
  28. return true
  29. }
  30. }
  31. async function cleanup(dir: string) {
  32. const entries = await fs.readdir(dir, { withFileTypes: true })
  33. const files = entries
  34. .filter((entry) => entry.isFile() && entry.name.endsWith(".log"))
  35. .map((entry) => path.join(dir, entry.name))
  36. if (files.length <= 5) return
  37. const filesToDelete = files.slice(0, -10)
  38. await Promise.all(
  39. filesToDelete.map((file) => fs.unlink(file).catch(() => {})),
  40. )
  41. }
  42. let last = Date.now()
  43. export function create(tags?: Record<string, any>) {
  44. tags = tags || {}
  45. function build(message: any, extra?: Record<string, any>) {
  46. const prefix = Object.entries({
  47. ...tags,
  48. ...extra,
  49. })
  50. .filter(([_, value]) => value !== undefined && value !== null)
  51. .map(([key, value]) => `${key}=${value}`)
  52. .join(" ")
  53. const next = new Date()
  54. const diff = next.getTime() - last
  55. last = next.getTime()
  56. return (
  57. [next.toISOString().split(".")[0], "+" + diff + "ms", prefix, message]
  58. .filter(Boolean)
  59. .join(" ") + "\n"
  60. )
  61. }
  62. const result = {
  63. info(message?: any, extra?: Record<string, any>) {
  64. process.stderr.write("INFO " + build(message, extra))
  65. },
  66. error(message?: any, extra?: Record<string, any>) {
  67. process.stderr.write("ERROR " + build(message, extra))
  68. },
  69. warn(message?: any, extra?: Record<string, any>) {
  70. process.stderr.write("WARN " + build(message, extra))
  71. },
  72. tag(key: string, value: string) {
  73. if (tags) tags[key] = value
  74. return result
  75. },
  76. clone() {
  77. return Log.create({ ...tags })
  78. },
  79. time(message: string, extra?: Record<string, any>) {
  80. const now = Date.now()
  81. result.info(message, { status: "started", ...extra })
  82. function stop() {
  83. result.info(message, {
  84. status: "completed",
  85. duration: Date.now() - now,
  86. ...extra,
  87. })
  88. }
  89. return {
  90. stop,
  91. [Symbol.dispose]() {
  92. stop()
  93. },
  94. }
  95. },
  96. }
  97. return result
  98. }
  99. }