UNPKG

strogger

Version:

📊 A modern structured logging library with functional programming, duck-typing, and comprehensive third-party integrations

223 lines • 9.25 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createFileTransport = void 0; const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); const node_util_1 = require("node:util"); const node_zlib_1 = require("node:zlib"); const types_1 = require("../types"); const errors_1 = require("../utils/errors"); const base_transport_1 = require("./base-transport"); const gzipAsync = (0, node_util_1.promisify)(node_zlib_1.gzip); const createFileTransport = (options = {}) => { const transportName = "File"; try { let minLevel = options.level ?? types_1.LogLevel.INFO; const formatter = options.formatter || { format: (entry) => JSON.stringify(entry), }; const filePath = options.filePath || process.env.LOG_FILE_PATH || "./logs/app.log"; const maxFileSize = options.maxFileSize ?? 10 * 1024 * 1024; // 10MB const maxFiles = options.maxFiles ?? 5; const rotationInterval = options.rotationInterval ?? 24 * 60 * 60 * 1000; // 24 hours const compressOldFiles = options.compressOldFiles ?? false; const dateFormat = options.dateFormat ?? "YYYY-MM-DD"; const encoding = options.encoding ?? "utf8"; const createSymlink = options.createSymlink ?? false; const symlinkName = options.symlinkName ?? "current.log"; // Validate required configuration (0, errors_1.validateEnvironmentVariable)("LOG_FILE_PATH", filePath, false); // Validate transport configuration (0, errors_1.validateTransportConfig)(transportName, { filePath }, ["filePath"]); const state = { currentFile: filePath, currentSize: 0, lastRotation: Date.now(), }; let flushTimer = null; const ensureDirectory = async (filePath) => { const dir = (0, node_path_1.dirname)(filePath); try { await node_fs_1.promises.access(dir); } catch { await node_fs_1.promises.mkdir(dir, { recursive: true }); } }; const getRotatedFileName = (originalPath, index) => { const ext = (0, node_path_1.extname)(originalPath); const base = originalPath.replace(ext, ""); const timestamp = new Date().toISOString().split("T")[0]; // YYYY-MM-DD const compressedExt = compressOldFiles ? ".gz" : ""; return `${base}.${timestamp}.${index}${ext}${compressedExt}`; }; const shouldRotate = () => { const timeSinceLastRotation = Date.now() - state.lastRotation; return (state.currentSize >= maxFileSize || timeSinceLastRotation >= rotationInterval); }; const compressFile = async (filePath) => { if (!compressOldFiles) return; try { const content = await node_fs_1.promises.readFile(filePath, encoding); const compressed = await gzipAsync(content); await node_fs_1.promises.writeFile(`${filePath}.gz`, compressed); await node_fs_1.promises.unlink(filePath); // Remove original file } catch (error) { (0, errors_1.handleTransportError)(error, transportName, true); } }; const rotateFiles = async () => { try { // Close current file handle if open if (state.fileHandle) { await state.fileHandle.close(); state.fileHandle = undefined; } // Rotate existing files for (let i = maxFiles - 1; i >= 1; i--) { const oldFile = getRotatedFileName(filePath, i); const newFile = getRotatedFileName(filePath, i + 1); try { await node_fs_1.promises.access(oldFile); await node_fs_1.promises.rename(oldFile, newFile); } catch { // File doesn't exist, continue } } // Move current file to .1 const rotatedFile = getRotatedFileName(filePath, 1); try { await node_fs_1.promises.access(filePath); await node_fs_1.promises.rename(filePath, rotatedFile); await compressFile(rotatedFile); } catch { // Current file doesn't exist, that's okay } // Reset state state.currentSize = 0; state.lastRotation = Date.now(); // Create new file await ensureDirectory(filePath); state.fileHandle = await node_fs_1.promises.open(filePath, "a"); // Create symlink if requested if (createSymlink) { const symlinkPath = (0, node_path_1.join)((0, node_path_1.dirname)(filePath), symlinkName); try { await node_fs_1.promises.unlink(symlinkPath); } catch { // Symlink doesn't exist, that's okay } await node_fs_1.promises.symlink((0, node_path_1.basename)(filePath), symlinkPath); } console.log(`[FILE] Rotated log file to: ${rotatedFile}`); } catch (error) { (0, errors_1.handleTransportError)(error, transportName, true); } }; const writeToFile = async (content) => { try { // Check if rotation is needed if (shouldRotate()) { await rotateFiles(); } // Ensure file handle is open if (!state.fileHandle) { await ensureDirectory(filePath); state.fileHandle = await node_fs_1.promises.open(filePath, "a"); } // Write content const logLine = `${content}\n`; await state.fileHandle.write(logLine, undefined, encoding); state.currentSize += logLine.length; } catch (error) { (0, errors_1.handleTransportError)(error, transportName, true); } }; const flush = async () => { try { if (state.fileHandle) { await state.fileHandle.sync(); } } catch (error) { (0, errors_1.handleTransportError)(error, transportName, true); } }; const startFlushTimer = () => { if (flushTimer) return; flushTimer = setInterval(() => { flush().catch((error) => { (0, errors_1.handleTransportError)(error, transportName, true); }); }, 5000); // Flush every 5 seconds }; // Start the flush timer startFlushTimer(); return { log: async (entry) => { if (!(0, base_transport_1.shouldLog)(entry.level, minLevel)) return; const formattedMessage = formatter.format(entry); await writeToFile(formattedMessage); }, setLevel: (level) => { minLevel = level; }, getLevel: () => minLevel, // File transport specific methods rotate: async () => { await rotateFiles(); }, getCurrentFile: () => state.currentFile, getCurrentSize: () => state.currentSize, flush: async () => { await flush(); }, close: async () => { if (flushTimer) { clearInterval(flushTimer); flushTimer = null; } if (state.fileHandle) { await state.fileHandle.close(); state.fileHandle = undefined; } }, // Get current configuration getConfig: () => ({ filePath, maxFileSize, maxFiles, rotationInterval, compressOldFiles, dateFormat, encoding, createSymlink, symlinkName, }), // Get transport statistics getStats: () => ({ currentFile: state.currentFile, currentSize: state.currentSize, lastRotation: state.lastRotation, fileHandleOpen: !!state.fileHandle, flushTimerActive: !!flushTimer, }), }; } catch (error) { (0, errors_1.handleTransportError)(error, transportName, false); throw error; // Re-throw for proper error handling } }; exports.createFileTransport = createFileTransport; //# sourceMappingURL=file-transport.js.map