Skip to main content
Module

std/log/rotating_file_handler.ts

The Deno Standard Library
Go to Latest
File
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.import type { LevelName } from "./levels.ts";import { existsSync } from "../fs/exists.ts";import { FileHandler, type FileHandlerOptions } from "./file_handler.ts";
interface RotatingFileHandlerOptions extends FileHandlerOptions { maxBytes: number; maxBackupCount: number;}
/** * This handler extends the functionality of the {@linkcode FileHandler} by * "rotating" the log file when it reaches a certain size. `maxBytes` specifies * the maximum size in bytes that the log file can grow to before rolling over * to a new one. If the size of the new log message plus the current log file * size exceeds `maxBytes` then a roll-over is triggered. When a roll-over * occurs, before the log message is written, the log file is renamed and * appended with `.1`. If a `.1` version already existed, it would have been * renamed `.2` first and so on. The maximum number of log files to keep is * specified by `maxBackupCount`. After the renames are complete the log message * is written to the original, now blank, file. * * Example: Given `log.txt`, `log.txt.1`, `log.txt.2` and `log.txt.3`, a * `maxBackupCount` of 3 and a new log message which would cause `log.txt` to * exceed `maxBytes`, then `log.txt.2` would be renamed to `log.txt.3` (thereby * discarding the original contents of `log.txt.3` since 3 is the maximum number * of backups to keep), `log.txt.1` would be renamed to `log.txt.2`, `log.txt` * would be renamed to `log.txt.1` and finally `log.txt` would be created from * scratch where the new log message would be written. * * This handler uses a buffer for writing log messages to file. Logs can be * manually flushed with `fileHandler.flush()`. Log messages with a log level * greater than ERROR are immediately flushed. Logs are also flushed on process * completion. * * Additional notes on `mode` as described above: * * - `'a'` Default mode. As above, this will pick up where the logs left off in * rotation, or create a new log file if it doesn't exist. * - `'w'` in addition to starting with a clean `filename`, this mode will also * cause any existing backups (up to `maxBackupCount`) to be deleted on setup * giving a fully clean slate. * - `'x'` requires that neither `filename`, nor any backups (up to * `maxBackupCount`), exist before setup. * * This handler requires both `--allow-read` and `--allow-write` permissions on * the log files. */export class RotatingFileHandler extends FileHandler { #maxBytes: number; #maxBackupCount: number; #currentFileSize = 0;
constructor(levelName: LevelName, options: RotatingFileHandlerOptions) { super(levelName, options); this.#maxBytes = options.maxBytes; this.#maxBackupCount = options.maxBackupCount; }
override setup() { if (this.#maxBytes < 1) { this.destroy(); throw new Error("maxBytes cannot be less than 1"); } if (this.#maxBackupCount < 1) { this.destroy(); throw new Error("maxBackupCount cannot be less than 1"); } super.setup();
if (this._mode === "w") { // Remove old backups too as it doesn't make sense to start with a clean // log file, but old backups for (let i = 1; i <= this.#maxBackupCount; i++) { try { Deno.removeSync(this._filename + "." + i); } catch (error) { if (!(error instanceof Deno.errors.NotFound)) { throw error; } } } } else if (this._mode === "x") { // Throw if any backups also exist for (let i = 1; i <= this.#maxBackupCount; i++) { if (existsSync(this._filename + "." + i)) { this.destroy(); throw new Deno.errors.AlreadyExists( "Backup log file " + this._filename + "." + i + " already exists", ); } } } else { this.#currentFileSize = (Deno.statSync(this._filename)).size; } }
override log(msg: string) { const msgByteLength = this._encoder.encode(msg).byteLength + 1;
if (this.#currentFileSize + msgByteLength > this.#maxBytes) { this.rotateLogFiles(); this.#currentFileSize = 0; }
super.log(msg);
this.#currentFileSize += msgByteLength; }
rotateLogFiles() { this.flush(); this._file!.close();
for (let i = this.#maxBackupCount - 1; i >= 0; i--) { const source = this._filename + (i === 0 ? "" : "." + i); const dest = this._filename + "." + (i + 1);
if (existsSync(source)) { Deno.renameSync(source, dest); } }
this._file = Deno.openSync(this._filename, this._openOptions); }}