Skip to main content
Module

std/log/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, LogLevels } from "./levels.ts";import type { LogRecord } from "./logger.ts";import { BaseHandler, type BaseHandlerOptions } from "./base_handler.ts";import { writeAllSync } from "../io/write_all.ts";
const PAGE_SIZE = 4096;export type LogMode = "a" | "w" | "x";
export interface FileHandlerOptions extends BaseHandlerOptions { filename: string; mode?: LogMode;}
/** * This handler will output to a file using an optional mode (default is `a`, * e.g. append). The file will grow indefinitely. It uses a buffer for writing * 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. * * Behavior of the log modes is as follows: * * - `'a'` - Default mode. Appends new log messages to the end of an existing log * file, or create a new log file if none exists. * - `'w'` - Upon creation of the handler, any existing log file will be removed * and a new one created. * - `'x'` - This will create a new log file and throw an error if one already * exists. * * This handler requires `--allow-write` permission on the log file. */export class FileHandler extends BaseHandler { protected _file: Deno.FsFile | undefined; protected _buf: Uint8Array = new Uint8Array(PAGE_SIZE); protected _pointer = 0; protected _filename: string; protected _mode: LogMode; protected _openOptions: Deno.OpenOptions; protected _encoder: TextEncoder = new TextEncoder(); #unloadCallback = (() => { this.destroy(); }).bind(this);
constructor(levelName: LevelName, options: FileHandlerOptions) { super(levelName, options); this._filename = options.filename; // default to append mode, write only this._mode = options.mode ? options.mode : "a"; this._openOptions = { createNew: this._mode === "x", create: this._mode !== "x", append: this._mode === "a", truncate: this._mode !== "a", write: true, }; }
override setup() { this._file = Deno.openSync(this._filename, this._openOptions); this.#resetBuffer();
addEventListener("unload", this.#unloadCallback); }
override handle(logRecord: LogRecord) { super.handle(logRecord);
// Immediately flush if log level is higher than ERROR if (logRecord.level > LogLevels.ERROR) { this.flush(); } }
override log(msg: string) { const bytes = this._encoder.encode(msg + "\n"); if (bytes.byteLength > this._buf.byteLength - this._pointer) { this.flush(); } if (bytes.byteLength > this._buf.byteLength) { writeAllSync(this._file!, bytes); } else { this._buf.set(bytes, this._pointer); this._pointer += bytes.byteLength; } }
flush() { if (this._pointer > 0 && this._file) { let written = 0; while (written < this._pointer) { written += this._file.writeSync( this._buf.subarray(written, this._pointer), ); } this.#resetBuffer(); } }
#resetBuffer() { this._pointer = 0; }
override destroy() { this.flush(); this._file?.close(); this._file = undefined; removeEventListener("unload", this.#unloadCallback); }}