UNPKG

@rollercoaster-dev/rd-logger

Version:

A neurodivergent-friendly logger for Rollercoaster.dev projects

152 lines (151 loc) 5.53 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import fs from 'fs'; import path from 'path'; import { safeStringify } from '../utils'; /** * File transport for the logger */ export class FileTransport { constructor(options) { this.name = 'file'; this.initialized = false; this.fileStream = null; this.logQueue = []; this.isProcessingQueue = false; this.filePath = options.filePath; } /** * Initialize the file transport (called lazily) */ initialize() { try { this.ensureLogDirectoryExists(); this.initializeFileStream(); this.initialized = true; } catch (error) { console.error(`Failed to initialize file transport: ${error.message}`); } } /** * Log a message to the file */ log(level, message, timestamp, context) { // Initialize lazily on first log attempt if (!this.initialized) { this.initialize(); // If initialization failed, fileStream will be null, and we can't log if (!this.initialized) { console.warn('FileTransport: Cannot log, initialization failed.'); return; } } // Create a plain text version for the file - always use ISO format for machine readability let fileEntry = `[${timestamp}] ${level.toUpperCase()}: ${message}`; if (Object.keys(context).length) { fileEntry += ` | ${safeStringify(context)}`; } // Add to queue and process asynchronously this.logQueue.push(fileEntry); this.processLogQueue(); } /** * Clean up resources used by the transport */ cleanup() { if (this.fileStream) { // Process any remaining logs in the queue if (this.logQueue.length > 0) { for (const entry of this.logQueue) { this.fileStream.write(entry + '\n'); } this.logQueue = []; } // Close the file stream this.fileStream.end(); this.fileStream = null; } } /** * Ensure the log directory exists */ ensureLogDirectoryExists() { const logDir = path.dirname(this.filePath); if (!fs.existsSync(logDir)) { fs.mkdirSync(logDir, { recursive: true }); } } /** * Initialize the file write stream */ initializeFileStream() { if (this.fileStream) { this.fileStream.end(); this.fileStream = null; } try { this.fileStream = fs.createWriteStream(this.filePath, { flags: 'a' }); // Handle stream errors this.fileStream.on('error', (error) => { console.error(`Error writing to log file: ${error.message}`); this.fileStream = null; }); // Process any queued logs once the stream is ready this.fileStream.on('ready', () => { this.processLogQueue(); }); } catch (error) { console.error(`Failed to create write stream for '${this.filePath}': ${error.message}`); } } /** * Process the log queue asynchronously */ processLogQueue() { return __awaiter(this, void 0, void 0, function* () { if (this.isProcessingQueue || this.logQueue.length === 0 || !this.fileStream) { return; } this.isProcessingQueue = true; try { while (this.logQueue.length > 0) { const entry = this.logQueue.shift(); if (entry && this.fileStream) { // Use a promise to handle backpressure const canContinue = this.fileStream.write(entry + '\n'); if (!canContinue) { // Wait for drain event before continuing yield new Promise((resolve) => { if (this.fileStream) { this.fileStream.once('drain', resolve); } else { resolve(); } }); } } } } catch (error) { console.error(`Error processing log queue: ${error.message}`); } finally { this.isProcessingQueue = false; // Check if more entries were added while processing if (this.logQueue.length > 0) { this.processLogQueue(); } } }); } }