@logtape/file
Version:
File sink and rotating file sink for LogTape
115 lines (113 loc) • 3.81 kB
JavaScript
import { defaultTextFormatter } from "@logtape/logtape";
//#region filesink.base.ts
/**
* Get a platform-independent file sink.
*
* @typeParam TFile The type of the file descriptor.
* @param path A path to the file to write to.
* @param options The options for the sink and the file driver.
* @returns A sink that writes to the file. The sink is also a disposable
* object that closes the file when disposed.
*/
function getBaseFileSink(path, options) {
const formatter = options.formatter ?? defaultTextFormatter;
const encoder = options.encoder ?? new TextEncoder();
const bufferSize = options.bufferSize ?? 1024 * 8;
const flushInterval = options.flushInterval ?? 5e3;
let fd = options.lazy ? null : options.openSync(path);
let buffer = "";
let lastFlushTimestamp = Date.now();
function flushBuffer() {
if (fd == null) return;
if (buffer.length > 0) {
options.writeSync(fd, encoder.encode(buffer));
buffer = "";
options.flushSync(fd);
lastFlushTimestamp = Date.now();
}
}
const sink = (record) => {
if (fd == null) fd = options.openSync(path);
buffer += formatter(record);
const shouldFlushBySize = buffer.length >= bufferSize;
const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
if (shouldFlushBySize || shouldFlushByTime) flushBuffer();
};
sink[Symbol.dispose] = () => {
if (fd !== null) {
flushBuffer();
options.closeSync(fd);
}
};
return sink;
}
/**
* Get a platform-independent rotating file sink.
*
* This sink writes log records to a file, and rotates the file when it reaches
* the `maxSize`. The rotated files are named with the original file name
* followed by a dot and a number, starting from 1. The number is incremented
* for each rotation, and the maximum number of files to keep is `maxFiles`.
*
* @param path A path to the file to write to.
* @param options The options for the sink and the file driver.
* @returns A sink that writes to the file. The sink is also a disposable
* object that closes the file when disposed.
*/
function getBaseRotatingFileSink(path, options) {
const formatter = options.formatter ?? defaultTextFormatter;
const encoder = options.encoder ?? new TextEncoder();
const maxSize = options.maxSize ?? 1024 * 1024;
const maxFiles = options.maxFiles ?? 5;
const bufferSize = options.bufferSize ?? 1024 * 8;
const flushInterval = options.flushInterval ?? 5e3;
let offset = 0;
try {
const stat = options.statSync(path);
offset = stat.size;
} catch {}
let fd = options.openSync(path);
let lastFlushTimestamp = Date.now();
function shouldRollover(bytes) {
return offset + bytes.length > maxSize;
}
function performRollover() {
options.closeSync(fd);
for (let i = maxFiles - 1; i > 0; i--) {
const oldPath = `${path}.${i}`;
const newPath = `${path}.${i + 1}`;
try {
options.renameSync(oldPath, newPath);
} catch (_) {}
}
options.renameSync(path, `${path}.1`);
offset = 0;
fd = options.openSync(path);
}
function flushBuffer() {
if (buffer.length > 0) {
const bytes = encoder.encode(buffer);
buffer = "";
if (shouldRollover(bytes)) performRollover();
options.writeSync(fd, bytes);
options.flushSync(fd);
offset += bytes.length;
lastFlushTimestamp = Date.now();
}
}
let buffer = "";
const sink = (record) => {
buffer += formatter(record);
const shouldFlushBySize = buffer.length >= bufferSize;
const shouldFlushByTime = flushInterval > 0 && record.timestamp - lastFlushTimestamp >= flushInterval;
if (shouldFlushBySize || shouldFlushByTime) flushBuffer();
};
sink[Symbol.dispose] = () => {
flushBuffer();
options.closeSync(fd);
};
return sink;
}
//#endregion
export { getBaseFileSink, getBaseRotatingFileSink };
//# sourceMappingURL=filesink.base.js.map