strogger
Version:
📊 A modern structured logging library with functional programming, duck-typing, and comprehensive third-party integrations
223 lines • 9.25 kB
JavaScript
;
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