UNPKG

@clipwhisperer/common

Version:

ClipWhisperer Common - Shared library providing core utilities, database schemas, authentication, bucket management, and common functionality across all ClipWhisperer microservices

376 lines (375 loc) 12.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.Logger = exports.FileTransport = exports.ConsoleTransport = void 0; const crypto_1 = require("crypto"); const fs_1 = require("fs"); const promises_1 = require("fs/promises"); const path_1 = require("path"); /** * Console transport for logging to stdout/stderr */ class ConsoleTransport { constructor(colors = true) { this.name = 'console'; this.colors = colors; } async write(entry) { const formatted = this.formatEntry(entry); if (entry.level === 'error' || entry.level === 'warn') { console.error(formatted); } else { console.log(formatted); } } formatEntry(entry) { const timestamp = entry.timestamp; const level = this.colors ? this.colorizeLevel(entry.level) : entry.level.toUpperCase(); const service = entry.service ? `[${entry.service}]` : ''; const component = entry.component ? `[${entry.component}]` : ''; const correlationId = entry.correlationId ? `(${entry.correlationId.slice(0, 8)})` : ''; let message = `${timestamp} ${level} ${service}${component}${correlationId} ${entry.message}`; if (entry.context && Object.keys(entry.context).length > 0) { message += ` | Context: ${JSON.stringify(entry.context)}`; } if (entry.error) { message += `\n Error: ${entry.error.name}: ${entry.error.message}`; if (entry.error.stack) { message += `\n Stack: ${entry.error.stack}`; } } return message; } colorizeLevel(level) { if (!this.colors) return level.toUpperCase(); const colors = { error: '\x1b[31m', // Red warn: '\x1b[33m', // Yellow info: '\x1b[36m', // Cyan debug: '\x1b[35m', // Magenta trace: '\x1b[37m', // White }; const reset = '\x1b[0m'; return `${colors[level]}${level.toUpperCase()}${reset}`; } } exports.ConsoleTransport = ConsoleTransport; /** * File transport for logging to files with rotation */ class FileTransport { constructor(logPath, maxFileSize = 10 * 1024 * 1024, // 10MB maxFiles = 5) { this.name = 'file'; this.writeStream = null; this.currentFileSize = 0; this.logPath = logPath; this.maxFileSize = maxFileSize; this.maxFiles = maxFiles; } async write(entry) { if (!this.writeStream) { await this.createWriteStream(); } const formatted = JSON.stringify(entry) + '\n'; // Check if we need to rotate the log file if (this.currentFileSize + formatted.length > this.maxFileSize) { await this.rotateLogFile(); } return new Promise((resolve, reject) => { this.writeStream.write(formatted, (error) => { if (error) { reject(error); } else { this.currentFileSize += formatted.length; resolve(); } }); }); } async flush() { if (this.writeStream) { return new Promise((resolve) => { this.writeStream.end(resolve); }); } } async close() { if (this.writeStream) { await this.flush(); this.writeStream = null; } } async createWriteStream() { // Ensure log directory exists await (0, promises_1.mkdir)((0, path_1.join)(this.logPath, '..'), { recursive: true }); this.writeStream = (0, fs_1.createWriteStream)(this.logPath, { flags: 'a' }); this.currentFileSize = 0; // Reset size counter } async rotateLogFile() { if (this.writeStream) { await this.close(); } // Rotate existing files for (let i = this.maxFiles - 1; i > 0; i--) { const oldFile = `${this.logPath}.${i}`; const newFile = `${this.logPath}.${i + 1}`; try { const fs = await Promise.resolve().then(() => __importStar(require('fs/promises'))); await fs.rename(oldFile, newFile); } catch { // File doesn't exist, continue } } // Move current log to .1 try { const fs = await Promise.resolve().then(() => __importStar(require('fs/promises'))); await fs.rename(this.logPath, `${this.logPath}.1`); } catch { // File doesn't exist, continue } await this.createWriteStream(); } } exports.FileTransport = FileTransport; /** * Enterprise-grade structured logger with multiple transports and correlation tracking */ class Logger { constructor() { this.transports = new Map(); this.correlationId = null; this.service = null; this.component = null; this.logLevels = { error: 0, warn: 1, info: 2, debug: 3, trace: 4, }; this.logLevel = process.env.LOG_LEVEL || 'info'; this.initializeTransports(); } static getInstance() { if (!Logger.instance) { Logger.instance = new Logger(); } return Logger.instance; } async initializeTransports() { // Add console transport this.addTransport(new ConsoleTransport(process.env.LOG_COLORS !== 'false')); // Add file transport if log directory is specified const logDir = process.env.LOG_DIRECTORY || './logs'; const logFile = (0, path_1.join)(logDir, 'application.log'); this.addTransport(new FileTransport(logFile)); } setCorrelationId(correlationId) { this.correlationId = correlationId; } setService(service) { this.service = service; } setComponent(component) { this.component = component; } clearContext() { this.correlationId = null; this.service = null; this.component = null; } child(context) { const childLogger = Object.create(this); childLogger.service = context.service || this.service; childLogger.component = context.component || this.component; childLogger.correlationId = context.correlationId || this.correlationId; return childLogger; } error(message, context, error) { this.log('error', message, context, error); } warn(message, context) { this.log('warn', message, context); } info(message, context) { this.log('info', message, context); } debug(message, context) { this.log('debug', message, context); } trace(message, context) { this.log('trace', message, context); } log(level, message, context, error) { if (!this.shouldLog(level)) return; const entry = { timestamp: new Date().toISOString(), level, message, context, correlationId: this.correlationId || undefined, service: this.service || undefined, component: this.component || undefined, error: error ? { name: error.name, message: error.message, stack: error.stack, } : undefined, metadata: { pid: process.pid, hostname: require('os').hostname(), version: process.version, }, }; // Write to all transports (fire and forget for performance) this.writeToTransports(entry).catch(console.error); } shouldLog(level) { return this.logLevels[level] <= this.logLevels[this.logLevel]; } async writeToTransports(entry) { const writePromises = Array.from(this.transports.values()).map(transport => transport.write(entry).catch(error => { console.error(`Failed to write to transport ${transport.name}:`, error); })); await Promise.allSettled(writePromises); } addTransport(transport) { this.transports.set(transport.name, transport); } removeTransport(name) { return this.transports.delete(name); } getTransports() { return Array.from(this.transports.values()); } async flush() { const flushPromises = Array.from(this.transports.values()) .filter(transport => transport.flush) .map(transport => transport.flush()); await Promise.allSettled(flushPromises); } async close() { const closePromises = Array.from(this.transports.values()) .filter(transport => transport.close) .map(transport => transport.close()); await Promise.allSettled(closePromises); this.transports.clear(); } async performance(operation, fn) { return this.performanceSync(operation, fn); } async performanceSync(operation, fn) { const startTime = Date.now(); const correlationId = this.generateCorrelationId(); this.info(`Starting operation: ${operation}`, { operation, correlationId, startTime: new Date(startTime).toISOString() }); try { const result = await fn(); const duration = Date.now() - startTime; this.info(`Operation completed: ${operation}`, { operation, correlationId, duration, status: 'success' }); return result; } catch (error) { const duration = Date.now() - startTime; this.error(`Operation failed: ${operation}`, { operation, correlationId, duration, status: 'error' }, error instanceof Error ? error : new Error(String(error))); throw error; } } metric(name, value, tags) { this.debug(`Metric: ${name}`, { metric: name, value, tags }); } counter(name, increment = 1, tags) { this.metric(name, increment, { ...tags, type: 'counter' }); } gauge(name, value, tags) { this.metric(name, value, { ...tags, type: 'gauge' }); } histogram(name, value, tags) { this.metric(name, value, { ...tags, type: 'histogram' }); } audit(action, resource, context) { this.info(`Audit: ${action}`, { audit: true, action, resource, ...context }); } security(event, details) { this.warn(`Security event: ${event}`, { security: true, event, ...details }); } health(service, status, details) { const level = status === 'healthy' ? 'info' : 'warn'; this.log(level, `Health check: ${service} is ${status}`, { health: true, service, status, ...details }); } static generateCorrelationId() { return (0, crypto_1.randomUUID)(); } generateCorrelationId() { return Logger.generateCorrelationId(); } } exports.Logger = Logger; // Export singleton instance for convenience exports.default = Logger.getInstance();