UNPKG

@logtape/file

Version:

File sink and rotating file sink for LogTape

115 lines (113 loc) 3.81 kB
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