{"version":3,"file":"file-log.mjs","names":[],"sources":["../../../../../../../@warlock.js/logger/src/channels/file-log.ts"],"sourcesContent":["import { ensureDirectoryAsync } from \"@warlock.js/fs\";\r\nimport dayjs from \"dayjs\";\r\nimport fs from \"fs\";\r\nimport { EOL } from \"os\";\r\nimport path from \"path\";\r\nimport { LogChannel } from \"../log-channel\";\r\nimport type {\r\n  BasicLogConfigurations,\r\n  LogContract,\r\n  LoggingData,\r\n  LogLevel,\r\n  LogMessage,\r\n} from \"../types\";\r\n\r\n// TODO: Add max messages per file before rotation\r\n\r\nexport type FileLogConfig = BasicLogConfigurations & {\r\n  storagePath?: string;\r\n  /**\r\n   * File name, without extension\r\n   */\r\n  name?: string;\r\n  /**\r\n   * chunk mode\r\n   * If set to `single`, the logs will be created in a single file, unless the rotate is set to true\r\n   * If set to `daily`, the logs will be created in a daily file, unless the rotate is set to true\r\n   * If set to `hourly`, the logs will be created in an hourly file, unless the rotate is set to true\r\n   * @default single\r\n   */\r\n  chunk?: \"single\" | \"daily\" | \"hourly\";\r\n  /**\r\n   * Whether to rotate the file\r\n   *\r\n   * @default true\r\n   */\r\n  rotate?: boolean;\r\n  /**\r\n   * File Extension\r\n   *\r\n   * @default log\r\n   */\r\n  extension?: string;\r\n  /**\r\n   * If rotate is set, the rotate name will be added to the file name suffixed with `-`\r\n   *\r\n   * @default DD-MM-YYYY\r\n   */\r\n  rotateFileName?: string;\r\n  /**\r\n   * Max file size before rotating the file\r\n   *\r\n   * @default 10MB\r\n   */\r\n  maxFileSize?: number;\r\n  /**\r\n   * Set the max messages that needs to be added before writing to the file\r\n   *\r\n   * @default 100\r\n   */\r\n  maxMessagesToWrite?: number;\r\n  /**\r\n   * Group logs by\r\n   * Please note that the order matters here\r\n   * For example, if you set `groupBy: ['level', 'module']`, the logs will be added in level name first, then by module\r\n   *\r\n   * @default none\r\n   */\r\n  groupBy?: (\"level\" | \"module\" | \"action\")[];\r\n  /**\r\n   * Define what levels should be logged\r\n   *\r\n   * @default all\r\n   */\r\n  levels?: LogLevel[];\r\n  /**\r\n   * Date and time format\r\n   */\r\n  dateFormat?: {\r\n    date?: string;\r\n    time?: string;\r\n  };\r\n};\r\n\r\nexport class FileLog extends LogChannel<FileLogConfig> implements LogContract {\r\n  /**\r\n   * {@inheritdoc}\r\n   */\r\n  public name = \"file\";\r\n\r\n  /**\r\n   * Messages buffer\r\n   */\r\n  protected messages: LogMessage[] = [];\r\n\r\n  /**\r\n   * Grouped messages\r\n   */\r\n  protected groupedMessages: Record<string, LogMessage[]> = {};\r\n\r\n  /**\r\n   * Default channel configurations\r\n   */\r\n  protected defaultConfigurations: FileLogConfig = {\r\n    storagePath: process.cwd() + \"/storage/logs\",\r\n    rotate: true,\r\n    name: \"app\",\r\n    extension: \"log\",\r\n    chunk: \"single\",\r\n    maxMessagesToWrite: 100,\r\n    filter: () => true,\r\n    maxFileSize: 10 * 1024 * 1024, // 10MB\r\n    get rotateFileName() {\r\n      return dayjs().format(\"DD-MM-YYYY\");\r\n    },\r\n    dateFormat: {\r\n      date: \"DD-MM-YYYY\",\r\n      time: \"HH:mm:ss\",\r\n    },\r\n  };\r\n\r\n  /**\r\n   * Last write time\r\n   */\r\n  protected lastWriteTime = Date.now();\r\n\r\n  /**\r\n   * A flag to determine if the file is being written\r\n   */\r\n  protected isWriting = false;\r\n\r\n  /**\r\n   * Handle for the periodic flush interval. Stored so it can be cleared\r\n   * in `dispose()` — long-lived processes that create channels dynamically\r\n   * would otherwise leak one timer per channel.\r\n   */\r\n  protected flushIntervalHandle?: NodeJS.Timeout;\r\n\r\n  /**\r\n   * Check file size for file rotation\r\n   */\r\n  protected async checkAndRotateFile(filePath = this.filePath) {\r\n    if (!this.config(\"rotate\")) return;\r\n\r\n    try {\r\n      const stats = await fs.promises.stat(filePath);\r\n      if (stats.size >= this.config(\"maxFileSize\")!) {\r\n        await this.rotateLogFile();\r\n      }\r\n    } catch (error: any) {\r\n      // ENOENT is expected when the file hasn't been created yet — there is\r\n      // nothing to rotate, so stay silent. Surface anything else.\r\n      if (error.code !== \"ENOENT\") {\r\n        console.error(\"Error checking log file:\", error);\r\n      }\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Rotate log file\r\n   */\r\n  protected async rotateLogFile() {\r\n    const fileName = `${this.fileName}-${this.config(\"rotateFileName\")}-${Date.now()}`;\r\n\r\n    const extension = this.extension;\r\n\r\n    const rotatedFilePath = path.join(this.storagePath, `${fileName}.${extension}`);\r\n\r\n    await fs.promises.rename(this.filePath, rotatedFilePath).catch((error) => {\r\n      console.error(\"Error rotating file:\", error);\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Flush messages\r\n   *\r\n   * Starts a periodic re-check so low-traffic channels don't sit on buffered\r\n   * entries indefinitely. The handle is stored on the instance so `dispose()`\r\n   * can stop it — without this, every channel leaks a timer for the lifetime\r\n   * of the process.\r\n   */\r\n  protected initMessageFlush() {\r\n    this.flushIntervalHandle = setInterval(() => {\r\n      if (\r\n        this.messages.length > 0 &&\r\n        (this.messages.length >= this.maxMessagesToWrite || Date.now() - this.lastWriteTime > 5000)\r\n      ) {\r\n        this.writeMessagesToFile();\r\n      }\r\n    }, 5000);\r\n  }\r\n\r\n  /**\r\n   * Stop the background flush interval and drain any buffered entries.\r\n   *\r\n   * Call this when discarding a channel (e.g. reconfiguring the logger at\r\n   * runtime) so the 5-second timer doesn't keep the event loop alive. Safe to\r\n   * call more than once.\r\n   */\r\n  public dispose(): void {\r\n    if (this.flushIntervalHandle) {\r\n      clearInterval(this.flushIntervalHandle);\r\n      this.flushIntervalHandle = undefined;\r\n    }\r\n\r\n    this.flushSync();\r\n  }\r\n\r\n  /**\r\n   * Get file path\r\n   */\r\n  public get filePath() {\r\n    const fileName = this.fileName;\r\n\r\n    const extension = this.extension;\r\n\r\n    return path.join(this.storagePath, `${fileName}.${extension}`);\r\n  }\r\n\r\n  /**\r\n   * Get max messages\r\n   */\r\n  protected get maxMessagesToWrite(): number {\r\n    return this.config(\"maxMessagesToWrite\")!;\r\n  }\r\n\r\n  /**\r\n   * Get file name\r\n   */\r\n  public get fileName(): string {\r\n    const debugLevel = this.config(\"chunk\")!;\r\n\r\n    switch (debugLevel) {\r\n      case \"single\":\r\n      default:\r\n        return this.config(\"name\")!;\r\n      case \"daily\":\r\n        return dayjs().format(\"DD-MM-YYYY\");\r\n      case \"hourly\":\r\n        return dayjs().format(\"DD-MM-YYYY-HH-00-00-a\");\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get file extension\r\n   */\r\n  public get extension(): string {\r\n    return this.config(\"extension\")!;\r\n  }\r\n\r\n  /**\r\n   * Get content\r\n   */\r\n  protected get content() {\r\n    return this.messages.map((message) => message.content).join(EOL) + EOL;\r\n  }\r\n\r\n  /**\r\n   * Get storage path\r\n   */\r\n  public get storagePath(): string {\r\n    return this.config(\"storagePath\")!;\r\n  }\r\n\r\n  /**\r\n   * {@inheritdoc}\r\n   */\r\n  protected async init() {\r\n    const logsDirectory = this.storagePath;\r\n\r\n    await ensureDirectoryAsync(logsDirectory);\r\n\r\n    this.initMessageFlush();\r\n  }\r\n\r\n  /**\r\n   * Synchronously flush messages\r\n   */\r\n  public flushSync(): void {\r\n    if (this.messages.length === 0 && Object.keys(this.groupedMessages).length === 0) return;\r\n\r\n    if (this.messagedShouldBeGrouped) {\r\n      this.prepareGroupedMessages();\r\n      for (const key in this.groupedMessages) {\r\n        const directoryPath = path.join(this.storagePath, key);\r\n        fs.mkdirSync(directoryPath, { recursive: true });\r\n        const filePath = path.join(directoryPath, `${this.fileName}.${this.extension}`);\r\n        const content = this.groupedMessages[key].map((message) => message.content).join(EOL) + EOL;\r\n        fs.appendFileSync(filePath, content);\r\n      }\r\n    } else {\r\n      fs.mkdirSync(this.storagePath, { recursive: true });\r\n      fs.appendFileSync(this.filePath, this.content);\r\n    }\r\n\r\n    this.onSave();\r\n  }\r\n\r\n  /**\r\n   * Asynchronously drain buffered entries to disk.\r\n   *\r\n   * The async analogue of {@link flushSync}: it reuses the same async writer\r\n   * as the background interval, so a caller on a graceful-shutdown path can\r\n   * `await channel.flush()` (or `await log.flush()`) and rely on the buffer\r\n   * being on disk once it resolves. `JSONFileLog` inherits this unchanged —\r\n   * its overridden `writeMessagesToFile` performs the JSON merge.\r\n   */\r\n  public async flush(): Promise<void> {\r\n    if (this.messages.length === 0 && Object.keys(this.groupedMessages).length === 0) {\r\n      return;\r\n    }\r\n\r\n    // Clear the in-flight lock so a deliberate drain is never short-circuited\r\n    // by a half-finished background write, then await the async writer to\r\n    // completion — unlike the fire-and-forget interval, callers depend on the\r\n    // buffer reaching disk before this resolves.\r\n    this.isWriting = false;\r\n\r\n    await this.writeMessagesToFile();\r\n  }\r\n\r\n  /**\r\n   * {@inheritdoc}\r\n   */\r\n  public async log(data: LoggingData) {\r\n    const { module, action, message, type: level, context } = data;\r\n\r\n    if (!this.shouldBeLogged(data)) return;\r\n\r\n    const { date: dateFormat, time } = this.getDateAndTimeFormat();\r\n\r\n    const date = dayjs().format(dateFormat + \" \" + time);\r\n\r\n    let content = `[${date}] [${level}] [${module}][${action}]: `;\r\n\r\n    let stack: string | undefined;\r\n\r\n    // check if message is an instance of Error\r\n    if (message instanceof Error) {\r\n      // in that case we need to store the error message and stack trace\r\n      content += message.message + EOL;\r\n      content += `[trace]` + EOL;\r\n      content += message.stack;\r\n      stack = message.stack;\r\n    } else {\r\n      content += message;\r\n    }\r\n\r\n    this.messages.push({\r\n      content,\r\n      level,\r\n      date,\r\n      module,\r\n      action,\r\n      stack,\r\n      context,\r\n      timestamp: new Date().toISOString(),\r\n    });\r\n\r\n    await this.checkIfMessagesShouldBeWritten(); // Immediate check on buffer size\r\n  }\r\n\r\n  /**\r\n   * Check if messages should be written\r\n   */\r\n  protected async checkIfMessagesShouldBeWritten() {\r\n    if (this.messages.length >= this.maxMessagesToWrite || Date.now() - this.lastWriteTime > 5000) {\r\n      await this.writeMessagesToFile();\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Should be called after messages are saved\r\n   */\r\n  protected onSave() {\r\n    this.messages = [];\r\n    this.groupedMessages = {};\r\n    this.isWriting = false;\r\n    this.lastWriteTime = Date.now();\r\n  }\r\n\r\n  /**\r\n   * Check if messages should be grouped\r\n   */\r\n  protected get messagedShouldBeGrouped(): boolean {\r\n    return Number(this.config(\"groupBy\")?.length) > 0;\r\n  }\r\n\r\n  /**\r\n   * Write messages to the file\r\n   */\r\n  protected async writeMessagesToFile() {\r\n    if (this.messages.length === 0 || this.isWriting || !this.isInitialized) return;\r\n\r\n    this.isWriting = true;\r\n\r\n    if (this.messagedShouldBeGrouped) {\r\n      return await this.writeGroupedMessagesToFile();\r\n    }\r\n\r\n    await this.checkAndRotateFile(); // Ensure we check file size before writing\r\n\r\n    try {\r\n      await this.write(this.filePath, this.content);\r\n      this.onSave();\r\n    } catch (error) {\r\n      console.error(\"Failed to write log:\", error);\r\n      // Implement fallback logic here\r\n      this.isWriting = false;\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Write grouped messages to the file\r\n   */\r\n  protected async writeGroupedMessagesToFile(): Promise<void> {\r\n    // first step, is to group the messages\r\n    this.prepareGroupedMessages();\r\n\r\n    // now each key in the grouped messages, represents the directory path that should extend the storage path\r\n    for (const key in this.groupedMessages) {\r\n      const directoryPath = path.join(this.storagePath, key);\r\n\r\n      await ensureDirectoryAsync(directoryPath);\r\n\r\n      const filePath = path.join(directoryPath, `${this.fileName}.${this.extension}`);\r\n\r\n      await this.checkAndRotateFile(filePath); // Ensure we check file size before writing\r\n\r\n      const content = this.groupedMessages[key].map((message) => message.content).join(EOL) + EOL;\r\n\r\n      try {\r\n        await this.write(filePath, content);\r\n      } catch (error) {\r\n        console.error(\"Failed to write log:\", error);\r\n      }\r\n    }\r\n\r\n    this.onSave();\r\n    this.isWriting = false;\r\n  }\r\n\r\n  /**\r\n   * Prepare grouped messages\r\n   */\r\n  protected prepareGroupedMessages(): void {\r\n    this.messages.forEach((message) => {\r\n      const key = this.config(\"groupBy\")!\r\n        .map((groupKey) => encodeURIComponent(message[groupKey]))\r\n        .join(\"/\");\r\n\r\n      this.groupedMessages[key] = this.groupedMessages[key] || [];\r\n      this.groupedMessages[key].push(message);\r\n    });\r\n  }\r\n\r\n  /**\r\n   * Start writing to the file\r\n   */\r\n  protected async write(filePath: string, content: string) {\r\n    return new Promise((resolve, reject) => {\r\n      const writer = fs.createWriteStream(filePath, { flags: \"a\" });\r\n\r\n      writer.write(content, (error) => {\r\n        writer.end();\r\n        if (error) {\r\n          reject(error);\r\n        } else {\r\n          resolve(true);\r\n        }\r\n      });\r\n    });\r\n  }\r\n}\r\n"],"mappings":"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"}