mirror of
https://github.com/denoland/std.git
synced 2024-11-22 04:59:05 +00:00
117 lines
3.4 KiB
TypeScript
117 lines
3.4 KiB
TypeScript
// 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 "@std/io/write-all";
|
|
|
|
export type LogMode = "a" | "w" | "x";
|
|
|
|
export interface FileHandlerOptions extends BaseHandlerOptions {
|
|
filename: string;
|
|
mode?: LogMode;
|
|
/**
|
|
* Buffer size for writing log messages to file, in bytes.
|
|
*
|
|
* @default {4096}
|
|
*/
|
|
bufferSize?: number;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
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,
|
|
};
|
|
this._buf = new Uint8Array(options.bufferSize ?? 4096);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|