UNPKG

mongodb-log-writer

Version:

A library for writing MongoDB logv2 messages

153 lines 6.22 kB
"use strict"; 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 { 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