2024-01-12 08:17:25 +00:00
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
2024-02-27 21:57:25 +00:00
|
|
|
import { type LevelName, LogLevels } from "./levels.ts";
|
2024-01-12 08:17:25 +00:00
|
|
|
import type { LogRecord } from "./logger.ts";
|
|
|
|
import { BaseHandler, type BaseHandlerOptions } from "./base_handler.ts";
|
2024-04-29 02:57:30 +00:00
|
|
|
import { writeAllSync } from "@std/io/write-all";
|
2024-08-20 03:18:30 +00:00
|
|
|
import {
|
|
|
|
bufSymbol,
|
|
|
|
encoderSymbol,
|
|
|
|
filenameSymbol,
|
|
|
|
fileSymbol,
|
|
|
|
modeSymbol,
|
|
|
|
openOptionsSymbol,
|
|
|
|
pointerSymbol,
|
|
|
|
} from "./_file_handler_symbols.ts";
|
2024-01-12 08:17:25 +00:00
|
|
|
|
2024-11-06 03:48:51 +00:00
|
|
|
/** Supported log modes for FileHandlerOptions {@linkcode FileHandlerOptions.mode}. */
|
2024-01-12 08:17:25 +00:00
|
|
|
export type LogMode = "a" | "w" | "x";
|
|
|
|
|
2024-11-06 03:48:51 +00:00
|
|
|
/** Options for {@linkcode FileHandler}. */
|
2024-01-12 08:17:25 +00:00
|
|
|
export interface FileHandlerOptions extends BaseHandlerOptions {
|
2024-11-06 03:48:51 +00:00
|
|
|
/**
|
|
|
|
* The filename to output to.
|
|
|
|
*/
|
2024-01-12 08:17:25 +00:00
|
|
|
filename: string;
|
refactor(archive,async,cli,csv,dotenv,encoding,expect,fmt,front-matter,fs,http,internal,log,net,path,semver,testing,text,webgpu,yaml): enable `"exactOptionalPropertyTypes"` option (#5892)
2024-09-04 05:15:01 +00:00
|
|
|
/**
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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.
|
|
|
|
*
|
refactor(archive,async,cli,csv,dotenv,encoding,expect,fmt,front-matter,fs,http,internal,log,net,path,semver,testing,text,webgpu,yaml): enable `"exactOptionalPropertyTypes"` option (#5892)
2024-09-04 05:15:01 +00:00
|
|
|
* @default {"a"}
|
|
|
|
*/
|
2024-01-12 08:17:25 +00:00
|
|
|
mode?: LogMode;
|
2024-05-07 04:48:48 +00:00
|
|
|
/**
|
|
|
|
* Buffer size for writing log messages to file, in bytes.
|
|
|
|
*
|
|
|
|
* @default {4096}
|
|
|
|
*/
|
|
|
|
bufferSize?: number;
|
2024-01-12 08:17:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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.
|
2024-01-12 08:17:25 +00:00
|
|
|
*
|
2024-11-06 03:48:51 +00:00
|
|
|
* This handler requires `--allow-write` permission on the log file.
|
2024-01-12 08:17:25 +00:00
|
|
|
*
|
2024-11-06 03:48:51 +00:00
|
|
|
* @example Usage
|
2024-11-13 11:06:03 +00:00
|
|
|
* ```ts no-assert ignore
|
2024-11-06 03:48:51 +00:00
|
|
|
* import { FileHandler } from "@std/log/file-handler";
|
2024-01-12 08:17:25 +00:00
|
|
|
*
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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
|
|
|
|
* ```
|
2024-01-12 08:17:25 +00:00
|
|
|
*/
|
|
|
|
export class FileHandler extends BaseHandler {
|
2024-11-06 03:48:51 +00:00
|
|
|
/** Opened file to append logs to.
|
|
|
|
* @example Usage
|
2024-11-13 11:06:03 +00:00
|
|
|
* ```ts no-assert ignore
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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
|
|
|
|
* ```
|
|
|
|
* **/
|
2024-08-20 03:18:30 +00:00
|
|
|
[fileSymbol]: Deno.FsFile | undefined;
|
2024-11-06 03:48:51 +00:00
|
|
|
/** Buffer used to write to file.
|
|
|
|
* @example Usage
|
2024-11-13 11:06:03 +00:00
|
|
|
* ```ts no-assert ignore
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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
|
|
|
|
* ```
|
|
|
|
* **/
|
2024-08-20 03:18:30 +00:00
|
|
|
[bufSymbol]: Uint8Array;
|
2024-11-06 03:48:51 +00:00
|
|
|
/** Current position for pointer.
|
|
|
|
* @example Usage
|
2024-11-13 11:06:03 +00:00
|
|
|
* ```ts no-assert ignore
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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
|
|
|
|
* ```
|
|
|
|
* **/
|
2024-08-20 03:18:30 +00:00
|
|
|
[pointerSymbol] = 0;
|
2024-11-06 03:48:51 +00:00
|
|
|
/** Filename associated with the file being logged.
|
|
|
|
* @example Usage
|
2024-11-13 11:06:03 +00:00
|
|
|
* ```ts no-assert ignore
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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
|
|
|
|
* ```
|
|
|
|
* **/
|
2024-08-20 03:18:30 +00:00
|
|
|
[filenameSymbol]: string;
|
2024-11-06 03:48:51 +00:00
|
|
|
/** Current log mode.
|
|
|
|
* @example Usage
|
2024-11-13 11:06:03 +00:00
|
|
|
* ```ts no-assert ignore
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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
|
|
|
|
* ```
|
|
|
|
* **/
|
2024-08-20 03:18:30 +00:00
|
|
|
[modeSymbol]: LogMode;
|
2024-11-06 03:48:51 +00:00
|
|
|
/** File open options.
|
|
|
|
* @example Usage
|
2024-11-13 11:06:03 +00:00
|
|
|
* ```ts no-assert ignore
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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
|
|
|
|
* ```
|
|
|
|
* **/
|
2024-08-20 03:18:30 +00:00
|
|
|
[openOptionsSymbol]: Deno.OpenOptions;
|
2024-11-06 03:48:51 +00:00
|
|
|
/** Text encoder.
|
|
|
|
* @example Usage
|
2024-11-13 11:06:03 +00:00
|
|
|
* ```ts no-assert ignore
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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
|
|
|
|
* ```
|
|
|
|
* **/
|
2024-08-20 03:18:30 +00:00
|
|
|
[encoderSymbol]: TextEncoder = new TextEncoder();
|
2024-01-12 08:17:25 +00:00
|
|
|
#unloadCallback = (() => {
|
|
|
|
this.destroy();
|
|
|
|
}).bind(this);
|
|
|
|
|
2024-11-06 03:48:51 +00:00
|
|
|
/**
|
|
|
|
* Constructs a new FileHandler instance.
|
|
|
|
*
|
|
|
|
* @param levelName The level name to log messages at.
|
|
|
|
* @param options Options for the handler.
|
|
|
|
*/
|
2024-01-12 08:17:25 +00:00
|
|
|
constructor(levelName: LevelName, options: FileHandlerOptions) {
|
|
|
|
super(levelName, options);
|
2024-08-20 03:18:30 +00:00
|
|
|
this[filenameSymbol] = options.filename;
|
2024-01-12 08:17:25 +00:00
|
|
|
// default to append mode, write only
|
refactor(archive,async,cli,csv,dotenv,encoding,expect,fmt,front-matter,fs,http,internal,log,net,path,semver,testing,text,webgpu,yaml): enable `"exactOptionalPropertyTypes"` option (#5892)
2024-09-04 05:15:01 +00:00
|
|
|
this[modeSymbol] = options.mode ?? "a";
|
2024-08-20 03:18:30 +00:00
|
|
|
this[openOptionsSymbol] = {
|
|
|
|
createNew: this[modeSymbol] === "x",
|
|
|
|
create: this[modeSymbol] !== "x",
|
|
|
|
append: this[modeSymbol] === "a",
|
|
|
|
truncate: this[modeSymbol] !== "a",
|
2024-01-12 08:17:25 +00:00
|
|
|
write: true,
|
|
|
|
};
|
2024-08-20 03:18:30 +00:00
|
|
|
this[bufSymbol] = new Uint8Array(options.bufferSize ?? 4096);
|
2024-01-12 08:17:25 +00:00
|
|
|
}
|
|
|
|
|
2024-11-06 03:48:51 +00:00
|
|
|
/**
|
|
|
|
* Sets up the file handler by opening the specified file and initializing resources.
|
|
|
|
*
|
|
|
|
* @example Usage
|
2024-11-13 11:06:03 +00:00
|
|
|
* ```ts no-assert ignore
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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();
|
|
|
|
* ```
|
|
|
|
*/
|
2024-01-12 08:17:25 +00:00
|
|
|
override setup() {
|
2024-08-20 03:18:30 +00:00
|
|
|
this[fileSymbol] = Deno.openSync(
|
|
|
|
this[filenameSymbol],
|
|
|
|
this[openOptionsSymbol],
|
|
|
|
);
|
2024-01-12 08:17:25 +00:00
|
|
|
this.#resetBuffer();
|
|
|
|
|
|
|
|
addEventListener("unload", this.#unloadCallback);
|
|
|
|
}
|
|
|
|
|
2024-11-06 03:48:51 +00:00
|
|
|
/**
|
|
|
|
* Handles a log record and flushes the buffer if the log level is higher than error.
|
|
|
|
*
|
|
|
|
* @param logRecord Log record to handle.
|
|
|
|
*
|
|
|
|
* @example Usage
|
2024-11-13 11:06:03 +00:00
|
|
|
* ```ts ignore
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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);
|
|
|
|
* ```
|
|
|
|
*/
|
2024-01-12 08:17:25 +00:00
|
|
|
override handle(logRecord: LogRecord) {
|
|
|
|
super.handle(logRecord);
|
|
|
|
|
|
|
|
// Immediately flush if log level is higher than ERROR
|
|
|
|
if (logRecord.level > LogLevels.ERROR) {
|
|
|
|
this.flush();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-06 03:48:51 +00:00
|
|
|
/**
|
|
|
|
* Logs a message by adding it to the buffer, with flushing as needed.
|
|
|
|
*
|
|
|
|
* @param msg The message to log.
|
|
|
|
*
|
|
|
|
* @example Usage
|
2024-11-13 11:06:03 +00:00
|
|
|
* ```ts ignore
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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);
|
|
|
|
* ```
|
|
|
|
*/
|
2024-08-22 06:46:53 +00:00
|
|
|
log(msg: string) {
|
2024-08-20 03:18:30 +00:00
|
|
|
const bytes = this[encoderSymbol].encode(msg + "\n");
|
|
|
|
if (bytes.byteLength > this[bufSymbol].byteLength - this[pointerSymbol]) {
|
2024-01-12 08:17:25 +00:00
|
|
|
this.flush();
|
|
|
|
}
|
2024-08-20 03:18:30 +00:00
|
|
|
if (bytes.byteLength > this[bufSymbol].byteLength) {
|
|
|
|
writeAllSync(this[fileSymbol]!, bytes);
|
2024-03-01 05:16:52 +00:00
|
|
|
} else {
|
2024-08-20 03:18:30 +00:00
|
|
|
this[bufSymbol].set(bytes, this[pointerSymbol]);
|
|
|
|
this[pointerSymbol] += bytes.byteLength;
|
2024-03-01 05:16:52 +00:00
|
|
|
}
|
2024-01-12 08:17:25 +00:00
|
|
|
}
|
|
|
|
|
2024-11-06 03:48:51 +00:00
|
|
|
/**
|
|
|
|
* Immediately writes the contents of the buffer to the previously opened file.
|
|
|
|
*
|
|
|
|
* @example Usage
|
2024-11-13 11:06:03 +00:00
|
|
|
* ```ts ignore
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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);
|
|
|
|
* ```
|
|
|
|
*/
|
2024-01-12 08:17:25 +00:00
|
|
|
flush() {
|
2024-08-20 03:18:30 +00:00
|
|
|
if (this[pointerSymbol] > 0 && this[fileSymbol]) {
|
2024-01-12 08:17:25 +00:00
|
|
|
let written = 0;
|
2024-08-20 03:18:30 +00:00
|
|
|
while (written < this[pointerSymbol]) {
|
|
|
|
written += this[fileSymbol].writeSync(
|
|
|
|
this[bufSymbol].subarray(written, this[pointerSymbol]),
|
2024-01-12 08:17:25 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
this.#resetBuffer();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#resetBuffer() {
|
2024-08-20 03:18:30 +00:00
|
|
|
this[pointerSymbol] = 0;
|
2024-01-12 08:17:25 +00:00
|
|
|
}
|
|
|
|
|
2024-11-06 03:48:51 +00:00
|
|
|
/**
|
|
|
|
* Destroys the handler, performing any cleanup that is required and closes the file handler.
|
|
|
|
*
|
|
|
|
* @example Usage
|
2024-11-13 11:06:03 +00:00
|
|
|
* ```ts ignore
|
2024-11-06 03:48:51 +00:00
|
|
|
* 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);
|
|
|
|
* ```
|
|
|
|
*/
|
2024-01-12 08:17:25 +00:00
|
|
|
override destroy() {
|
|
|
|
this.flush();
|
2024-08-20 03:18:30 +00:00
|
|
|
this[fileSymbol]?.close();
|
|
|
|
this[fileSymbol] = undefined;
|
2024-01-12 08:17:25 +00:00
|
|
|
removeEventListener("unload", this.#unloadCallback);
|
|
|
|
}
|
|
|
|
}
|