mongodb-log-writer
Version:
A library for writing MongoDB logv2 messages
154 lines • 6.23 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MongoLogManager = void 0;
const path_1 = __importDefault(require("path"));
const bson_1 = require("bson");
const events_1 = require("events");
const fs_1 = require("fs");
const zlib_1 = require("zlib");
const heap_js_1 = require("heap-js");
const mongo_log_writer_1 = require("./mongo-log-writer");
const stream_1 = require("stream");
class MongoLogManager {
_options;
constructor(options) {
if (options.prefix) {
if (!/^[a-z0-9_]+$/i.test(options.prefix)) {
throw new Error('Prefix must only contain letters, numbers, and underscores');
}
}
this._options = options;
}
async deleteFile(path) {
try {
await fs_1.promises.unlink(path);
}
catch (err) {
if (err?.code !== 'ENOENT') {
this._options.onerror(err, path);
}
}
}
get prefix() {
return this._options.prefix ?? '';
}
async cleanupOldLogFiles(maxDurationMs = 5_000, remainingRetries = 1) {
const deletionStartTimestamp = Date.now();
const deletionCutoffTimestamp = deletionStartTimestamp - this._options.retentionDays * 86400 * 1000;
const dir = this._options.directory;
let dirHandle;
try {
dirHandle = await fs_1.promises.opendir(dir);
}
catch {
return;
}
const leastRecentFileHeap = new heap_js_1.Heap((a, b) => a.fileTimestamp - b.fileTimestamp);
const hasRetentionGB = !!this._options.retentionGB && isFinite(this._options.retentionGB);
const hasMaxLogFileCount = !!this._options.maxLogFileCount &&
isFinite(this._options.maxLogFileCount);
let usedStorageSize = hasRetentionGB ? 0 : -Infinity;
try {
for await (const dirent of dirHandle) {
if (Date.now() - deletionStartTimestamp > maxDurationMs)
break;
if (!dirent.isFile())
continue;
const logRegExp = new RegExp(`^${this.prefix}(?<id>[a-f0-9]{24})_log(\\.gz)?$`, 'i');
const { id } = logRegExp.exec(dirent.name)?.groups ?? {};
if (!id)
continue;
const fileTimestamp = +new bson_1.ObjectId(id).getTimestamp();
const fullPath = path_1.default.join(dir, dirent.name);
if (fileTimestamp < deletionCutoffTimestamp) {
await this.deleteFile(fullPath);
continue;
}
let fileSize;
if (hasRetentionGB) {
try {
fileSize = (await fs_1.promises.stat(fullPath)).size;
usedStorageSize += fileSize;
}
catch (err) {
this._options.onerror(err, fullPath);
continue;
}
}
if (hasMaxLogFileCount || hasRetentionGB) {
leastRecentFileHeap.push({ fullPath, fileTimestamp, fileSize });
}
if (hasMaxLogFileCount &&
this._options.maxLogFileCount &&
leastRecentFileHeap.size() > this._options.maxLogFileCount) {
const toDelete = leastRecentFileHeap.pop();
if (!toDelete)
continue;
await this.deleteFile(toDelete.fullPath);
usedStorageSize -= toDelete.fileSize ?? 0;
}
}
}
catch (statErr) {
if (statErr.code === 'ENOENT' && remainingRetries > 0) {
await this.cleanupOldLogFiles(maxDurationMs - (Date.now() - deletionStartTimestamp), remainingRetries - 1);
}
}
if (hasRetentionGB && this._options.retentionGB) {
const storageSizeLimit = this._options.retentionGB * 1024 * 1024 * 1024;
for (const file of leastRecentFileHeap) {
if (Date.now() - deletionStartTimestamp > maxDurationMs)
break;
if (usedStorageSize <= storageSizeLimit)
break;
if (!file.fileSize)
continue;
await this.deleteFile(file.fullPath);
usedStorageSize -= file.fileSize;
}
}
}
async createLogWriter() {
const logId = new bson_1.ObjectId().toString();
const doGzip = !!this._options.gzip;
const logFilePath = path_1.default.join(this._options.directory, `${this.prefix}${logId}_log${doGzip ? '.gz' : ''}`);
let originalTarget;
let stream;
let logWriter;
try {
stream = (0, fs_1.createWriteStream)(logFilePath, { mode: 0o600 });
originalTarget = stream;
await (0, events_1.once)(stream, 'ready');
if (doGzip) {
stream = (0, zlib_1.createGzip)({
flush: zlib_1.constants.Z_SYNC_FLUSH,
level: zlib_1.constants.Z_MAX_LEVEL,
});
stream.pipe(originalTarget);
}
else {
stream.on('finish', () => stream.emit('log-finish'));
}
}
catch (err) {
this._options.onwarn(err, logFilePath);
stream = new stream_1.Writable({
write(chunk, enc, cb) {
cb();
},
});
originalTarget = stream;
logWriter = new mongo_log_writer_1.MongoLogWriter(logId, null, stream);
}
if (!logWriter) {
logWriter = new mongo_log_writer_1.MongoLogWriter(logId, logFilePath, stream);
}
originalTarget.on('finish', () => logWriter?.emit('log-finish'));
return logWriter;
}
}
exports.MongoLogManager = MongoLogManager;
//# sourceMappingURL=mongo-log-manager.js.map