log.ts 2.9 KB

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