Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(log): document FileHandler #6175

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions _tools/check_docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const ENTRY_POINTS = [
"../json/mod.ts",
"../jsonc/mod.ts",
"../log/base_handler.ts",
"../log/file_handler.ts",
"../log/warn.ts",
"../log/critical.ts",
"../log/debug.ts",
Expand Down
223 changes: 210 additions & 13 deletions log/file_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,25 @@ import {
pointerSymbol,
} from "./_file_handler_symbols.ts";

/** Supported log modes for FileHandlerOptions {@linkcode FileHandlerOptions.mode}. */
export type LogMode = "a" | "w" | "x";

/** Options for {@linkcode FileHandler}. */
export interface FileHandlerOptions extends BaseHandlerOptions {
/**
* The filename to output to.
*/
filename: string;
/**
* Log mode for the handler. 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.
*
* @default {"a"}
*/
mode?: LogMode;
Expand All @@ -30,35 +44,126 @@ export interface FileHandlerOptions extends BaseHandlerOptions {
}

/**
* 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.
* A file-based log handler that writes log messages to a specified file with buffering and optional modes.
* The logs are buffered for optimized performance, writing to the file only
* when the buffer is full, on manual .flush() call, during logging of a critical message or when process ends.
* It is important to note that the file can grow indefinitely.
*
* Behavior of the log modes is as follows:
* This handler requires `--allow-write` permission on the log file.
*
* - `'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.
* @example Usage
* ```ts no-assert
* import { FileHandler } from "@std/log/file-handler";
*
* This handler requires `--allow-write` permission on the log file.
* const handler = new FileHandler("INFO", { filename: "./logs.txt" });
* handler.setup();
* handler.log('Hello, world!'); // Buffers the message, or writes it to the file depending on buffer state
* handler.flush(); // Manually flushes the buffer
* handler.destroy(); // Closes the file and removes listeners
* ```
*/
export class FileHandler extends BaseHandler {
/** Opened file to append logs to.
* @example Usage
* ```ts no-assert
* import { FileHandler } from "@std/log/file-handler";
*
* const handler = new FileHandler("INFO", { filename: "./logs.txt" });
* handler.setup();
* handler.log('Hello, world!'); // Buffers the message, or writes it to the file depending on buffer state
* handler.flush(); // Manually flushes the buffer
* handler.destroy(); // Closes the file and removes listeners
* ```
* **/
[fileSymbol]: Deno.FsFile | undefined;
/** Buffer used to write to file.
* @example Usage
* ```ts no-assert
* import { FileHandler } from "@std/log/file-handler";
*
* const handler = new FileHandler("INFO", { filename: "./logs.txt" });
* handler.setup();
* handler.log('Hello, world!'); // Buffers the message, or writes it to the file depending on buffer state
* handler.flush(); // Manually flushes the buffer
* handler.destroy(); // Closes the file and removes listeners
* ```
* **/
[bufSymbol]: Uint8Array;
/** Current position for pointer.
* @example Usage
* ```ts no-assert
* import { FileHandler } from "@std/log/file-handler";
*
* const handler = new FileHandler("INFO", { filename: "./logs.txt" });
* handler.setup();
* handler.log('Hello, world!'); // Buffers the message, or writes it to the file depending on buffer state
* handler.flush(); // Manually flushes the buffer
* handler.destroy(); // Closes the file and removes listeners
* ```
* **/
[pointerSymbol] = 0;
/** Filename associated with the file being logged.
* @example Usage
* ```ts no-assert
* import { FileHandler } from "@std/log/file-handler";
*
* const handler = new FileHandler("INFO", { filename: "./logs.txt" });
* handler.setup();
* handler.log('Hello, world!'); // Buffers the message, or writes it to the file depending on buffer state
* handler.flush(); // Manually flushes the buffer
* handler.destroy(); // Closes the file and removes listeners
* ```
* **/
[filenameSymbol]: string;
/** Current log mode.
* @example Usage
* ```ts no-assert
* import { FileHandler } from "@std/log/file-handler";
*
* const handler = new FileHandler("INFO", { filename: "./logs.txt" });
* handler.setup();
* handler.log('Hello, world!'); // Buffers the message, or writes it to the file depending on buffer state
* handler.flush(); // Manually flushes the buffer
* handler.destroy(); // Closes the file and removes listeners
* ```
* **/
[modeSymbol]: LogMode;
/** File open options.
* @example Usage
* ```ts no-assert
* import { FileHandler } from "@std/log/file-handler";
*
* const handler = new FileHandler("INFO", { filename: "./logs.txt" });
* handler.setup();
* handler.log('Hello, world!'); // Buffers the message, or writes it to the file depending on buffer state
* handler.flush(); // Manually flushes the buffer
* handler.destroy(); // Closes the file and removes listeners
* ```
* **/
[openOptionsSymbol]: Deno.OpenOptions;
/** Text encoder.
* @example Usage
* ```ts no-assert
* import { FileHandler } from "@std/log/file-handler";
*
* const handler = new FileHandler("INFO", { filename: "./logs.txt" });
* handler.setup();
* handler.log('Hello, world!'); // Buffers the message, or writes it to the file depending on buffer state
* handler.flush(); // Manually flushes the buffer
* handler.destroy(); // Closes the file and removes listeners
* ```
* **/
[encoderSymbol]: TextEncoder = new TextEncoder();
Comment on lines +66 to 156
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these symbols should probably be private properties, but since they are not, linting requires them to be documented still. I'm not sure how to handle this better.

#unloadCallback = (() => {
this.destroy();
}).bind(this);

/**
* Constructs a new FileHandler instance.
*
* @param levelName The level name to log messages at.
* @param options Options for the handler.
*/
constructor(levelName: LevelName, options: FileHandlerOptions) {
super(levelName, options);
this[filenameSymbol] = options.filename;
Expand All @@ -74,6 +179,18 @@ export class FileHandler extends BaseHandler {
this[bufSymbol] = new Uint8Array(options.bufferSize ?? 4096);
}

/**
* Sets up the file handler by opening the specified file and initializing resources.
*
* @example Usage
* ```ts no-assert
* import { FileHandler } from "@std/log/file-handler";
*
* const handler = new FileHandler("INFO", { filename: "./logs.txt" });
* handler.setup(); // Opens the file and prepares the handler for logging.
* handler.destroy();
* ```
*/
override setup() {
this[fileSymbol] = Deno.openSync(
this[filenameSymbol],
Expand All @@ -84,6 +201,35 @@ export class FileHandler extends BaseHandler {
addEventListener("unload", this.#unloadCallback);
}

/**
* Handles a log record and flushes the buffer if the log level is higher than error.
*
* @param logRecord Log record to handle.
*
* @example Usage
* ```ts
* import { FileHandler } from "@std/log/file-handler";
* import { assertInstanceOf } from "@std/assert/instance-of";
* import { LogLevels } from "./levels.ts";
* import { LogRecord } from "./logger.ts";
*
* const handler = new FileHandler("INFO", { filename: "./logs.txt" });
* handler.setup();
*
* // Flushes the buffer immediately and logs "CRITICAL This log is very critical indeed." into the file.
* handler.handle(
* new LogRecord({
* msg: "This log is very critical indeed.",
* args: [],
* level: LogLevels.CRITICAL,
* loggerName: "INFO",
* }),
* );
* handler.destroy();
*
* assertInstanceOf(handler, FileHandler);
* ```
*/
override handle(logRecord: LogRecord) {
super.handle(logRecord);

Expand All @@ -93,6 +239,25 @@ export class FileHandler extends BaseHandler {
}
}

/**
* Logs a message by adding it to the buffer, with flushing as needed.
*
* @param msg The message to log.
*
* @example Usage
* ```ts
* import { FileHandler } from "@std/log/file-handler";
* import { assertInstanceOf } from "@std/assert/instance-of";
*
* const handler = new FileHandler("INFO", { filename: "./logs.txt" });
* handler.setup();
* handler.log('Hello, world!');
* handler.flush();
* handler.destroy();
*
* assertInstanceOf(handler, FileHandler);
* ```
*/
log(msg: string) {
const bytes = this[encoderSymbol].encode(msg + "\n");
if (bytes.byteLength > this[bufSymbol].byteLength - this[pointerSymbol]) {
Expand All @@ -106,6 +271,23 @@ export class FileHandler extends BaseHandler {
}
}

/**
* Immediately writes the contents of the buffer to the previously opened file.
*
* @example Usage
* ```ts
* import { FileHandler } from "@std/log/file-handler";
* import { assertInstanceOf } from "@std/assert/instance-of";
*
* const handler = new FileHandler("INFO", { filename: "./logs.txt" });
* handler.setup();
* handler.log('Hello, world!');
* handler.flush(); // Writes buffered log messages to the file immediately.
* handler.destroy();
*
* assertInstanceOf(handler, FileHandler);
* ```
*/
flush() {
if (this[pointerSymbol] > 0 && this[fileSymbol]) {
let written = 0;
Expand All @@ -122,6 +304,21 @@ export class FileHandler extends BaseHandler {
this[pointerSymbol] = 0;
}

/**
* Destroys the handler, performing any cleanup that is required and closes the file handler.
*
* @example Usage
* ```ts
* import { FileHandler } from "@std/log/file-handler";
* import { assertInstanceOf } from "@std/assert/instance-of";
*
* const handler = new FileHandler("INFO", { filename: "./logs.txt" });
* handler.setup();
* handler.destroy();
*
* assertInstanceOf(handler, FileHandler);
* ```
*/
override destroy() {
this.flush();
this[fileSymbol]?.close();
Expand Down