insta-meme-bot-cli
Version:
Production-ready Instagram meme bot that scrapes memes from Pinterest and posts them automatically with CLI and programmatic API support
278 lines (241 loc) • 7.16 kB
JavaScript
/**
* Advanced logging system for Instagram Meme Bot
* Ultra-scalable with multiple log levels and outputs
*/
const fs = require('fs');
const path = require('path');
class Logger {
constructor(options = {}) {
this.logLevel = options.logLevel || 'info';
this.logFile = options.logFile || 'bot.log';
this.maxFileSize = options.maxFileSize || 10 * 1024 * 1024; // 10MB
this.maxFiles = options.maxFiles || 5;
this.enableConsole = options.enableConsole !== false;
this.enableFile = options.enableFile !== false;
this.levels = {
error: 0,
warn: 1,
info: 2,
debug: 3,
trace: 4
};
this.colors = {
error: '\x1b[31m', // Red
warn: '\x1b[33m', // Yellow
info: '\x1b[36m', // Cyan
debug: '\x1b[35m', // Magenta
trace: '\x1b[37m', // White
reset: '\x1b[0m'
};
this.emojis = {
error: '❌',
warn: '⚠️',
info: 'ℹ️',
debug: '🐛',
trace: '🔍'
};
this.initializeLogFile();
}
/**
* Initialize log file and handle rotation
*/
initializeLogFile() {
if (!this.enableFile) return;
try {
const logDir = path.dirname(this.logFile);
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
// Check if log rotation is needed
if (fs.existsSync(this.logFile)) {
const stats = fs.statSync(this.logFile);
if (stats.size > this.maxFileSize) {
this.rotateLogFile();
}
}
} catch (error) {
console.error('Failed to initialize log file:', error.message);
}
}
/**
* Rotate log files when they get too large
*/
rotateLogFile() {
try {
const logDir = path.dirname(this.logFile);
const logName = path.basename(this.logFile, path.extname(this.logFile));
const logExt = path.extname(this.logFile);
// Shift existing log files
for (let i = this.maxFiles - 1; i > 0; i--) {
const oldFile = path.join(logDir, `${logName}.${i}${logExt}`);
const newFile = path.join(logDir, `${logName}.${i + 1}${logExt}`);
if (fs.existsSync(oldFile)) {
if (i === this.maxFiles - 1) {
fs.unlinkSync(oldFile); // Delete oldest
} else {
fs.renameSync(oldFile, newFile);
}
}
}
// Move current log to .1
const firstRotated = path.join(logDir, `${logName}.1${logExt}`);
if (fs.existsSync(this.logFile)) {
fs.renameSync(this.logFile, firstRotated);
}
} catch (error) {
console.error('Failed to rotate log file:', error.message);
}
}
/**
* Check if message should be logged based on level
* @param {string} level - Log level
* @returns {boolean} Should log
*/
shouldLog(level) {
return this.levels[level] <= this.levels[this.logLevel];
}
/**
* Format log message
* @param {string} level - Log level
* @param {string} message - Message to log
* @param {Object} meta - Additional metadata
* @returns {string} Formatted message
*/
formatMessage(level, message, meta = {}) {
const timestamp = new Date().toISOString();
const emoji = this.emojis[level] || '';
const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : '';
return `[${timestamp}] ${emoji} ${level.toUpperCase()}: ${message}${metaStr}`;
}
/**
* Write to console with colors
* @param {string} level - Log level
* @param {string} message - Formatted message
*/
writeToConsole(level, message) {
if (!this.enableConsole) return;
const color = this.colors[level] || this.colors.reset;
const coloredMessage = `${color}${message}${this.colors.reset}`;
if (level === 'error') {
console.error(coloredMessage);
} else if (level === 'warn') {
console.warn(coloredMessage);
} else {
console.log(coloredMessage);
}
}
/**
* Write to file
* @param {string} message - Formatted message
*/
writeToFile(message) {
if (!this.enableFile) return;
try {
// Check if rotation is needed before writing
if (fs.existsSync(this.logFile)) {
const stats = fs.statSync(this.logFile);
if (stats.size > this.maxFileSize) {
this.rotateLogFile();
}
}
fs.appendFileSync(this.logFile, message + '\n');
} catch (error) {
console.error('Failed to write to log file:', error.message);
}
}
/**
* Generic log method
* @param {string} level - Log level
* @param {string} message - Message to log
* @param {Object} meta - Additional metadata
*/
log(level, message, meta = {}) {
if (!this.shouldLog(level)) return;
const formattedMessage = this.formatMessage(level, message, meta);
this.writeToConsole(level, formattedMessage);
this.writeToFile(formattedMessage);
}
/**
* Error level logging
* @param {string} message - Error message
* @param {Object} meta - Additional metadata
*/
error(message, meta = {}) {
this.log('error', message, meta);
}
/**
* Warning level logging
* @param {string} message - Warning message
* @param {Object} meta - Additional metadata
*/
warn(message, meta = {}) {
this.log('warn', message, meta);
}
/**
* Info level logging
* @param {string} message - Info message
* @param {Object} meta - Additional metadata
*/
info(message, meta = {}) {
this.log('info', message, meta);
}
/**
* Debug level logging
* @param {string} message - Debug message
* @param {Object} meta - Additional metadata
*/
debug(message, meta = {}) {
this.log('debug', message, meta);
}
/**
* Trace level logging
* @param {string} message - Trace message
* @param {Object} meta - Additional metadata
*/
trace(message, meta = {}) {
this.log('trace', message, meta);
}
/**
* Set log level
* @param {string} level - New log level
*/
setLevel(level) {
if (this.levels[level] !== undefined) {
this.logLevel = level;
}
}
/**
* Create child logger with additional context
* @param {Object} context - Additional context for all logs
* @returns {Logger} Child logger
*/
child(context = {}) {
const childLogger = new Logger({
logLevel: this.logLevel,
logFile: this.logFile,
enableConsole: this.enableConsole,
enableFile: this.enableFile
});
// Override log method to include context
const originalLog = childLogger.log.bind(childLogger);
childLogger.log = (level, message, meta = {}) => {
const mergedMeta = { ...context, ...meta };
originalLog(level, message, mergedMeta);
};
return childLogger;
}
/**
* Clear log file
*/
clear() {
if (this.enableFile && fs.existsSync(this.logFile)) {
try {
fs.writeFileSync(this.logFile, '');
} catch (error) {
console.error('Failed to clear log file:', error.message);
}
}
}
}
// Export singleton instance
module.exports = new Logger();