package logger import ( "fmt" "log" "os" "strings" "time" ) // LogLevel represents different log levels type LogLevel int const ( DEBUG LogLevel = iota INFO WARN ERROR OPPORTUNITY // Special level for opportunities ) var logLevelNames = map[LogLevel]string{ DEBUG: "DEBUG", INFO: "INFO", WARN: "WARN", ERROR: "ERROR", OPPORTUNITY: "OPPORTUNITY", } // Logger represents a multi-file logger with separation of concerns type Logger struct { // Main application logger logger *log.Logger level LogLevel // Specialized loggers for different concerns opportunityLogger *log.Logger // MEV opportunities and arbitrage attempts errorLogger *log.Logger // Errors and warnings only performanceLogger *log.Logger // Performance metrics and RPC calls transactionLogger *log.Logger // Detailed transaction analysis // Security filtering secureFilter *SecureFilter levelName string } // parseLogLevel converts string log level to LogLevel enum func parseLogLevel(level string) LogLevel { switch strings.ToLower(level) { case "debug": return DEBUG case "info": return INFO case "warn", "warning": return WARN case "error": return ERROR default: return INFO // Default to INFO level } } // createLogFile creates a log file or returns stdout if it fails func createLogFile(filename string) *os.File { if filename == "" { return os.Stdout } f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { log.Printf("Failed to create log file %s: %v, falling back to stdout", filename, err) return os.Stdout } return f } // New creates a new multi-file logger with separation of concerns func New(level string, format string, file string) *Logger { // Parse base filename for specialized logs baseDir := "logs" baseName := "mev_bot" if file != "" { // Extract directory and base filename parts := strings.Split(file, "/") if len(parts) > 1 { baseDir = strings.Join(parts[:len(parts)-1], "/") } filename := parts[len(parts)-1] if strings.Contains(filename, ".") { baseName = strings.Split(filename, ".")[0] } } // Create specialized log files mainFile := createLogFile(file) opportunityFile := createLogFile(fmt.Sprintf("%s/%s_opportunities.log", baseDir, baseName)) errorFile := createLogFile(fmt.Sprintf("%s/%s_errors.log", baseDir, baseName)) performanceFile := createLogFile(fmt.Sprintf("%s/%s_performance.log", baseDir, baseName)) transactionFile := createLogFile(fmt.Sprintf("%s/%s_transactions.log", baseDir, baseName)) // Create loggers with no prefixes (we format ourselves) logLevel := parseLogLevel(level) // Determine security level based on environment and log level var securityLevel SecurityLevel env := os.Getenv("GO_ENV") switch { case env == "production": securityLevel = SecurityLevelProduction case logLevel >= WARN: securityLevel = SecurityLevelInfo default: securityLevel = SecurityLevelDebug } return &Logger{ logger: log.New(mainFile, "", 0), opportunityLogger: log.New(opportunityFile, "", 0), errorLogger: log.New(errorFile, "", 0), performanceLogger: log.New(performanceFile, "", 0), transactionLogger: log.New(transactionFile, "", 0), level: logLevel, secureFilter: NewSecureFilter(securityLevel), levelName: level, } } // shouldLog determines if a message should be logged based on level func (l *Logger) shouldLog(level LogLevel) bool { return level >= l.level } // formatMessage formats a log message with timestamp and level func (l *Logger) formatMessage(level LogLevel, v ...interface{}) string { timestamp := time.Now().Format("2006/01/02 15:04:05") levelName := logLevelNames[level] message := fmt.Sprint(v...) return fmt.Sprintf("%s [%s] %s", timestamp, levelName, message) } // Debug logs a debug message func (l *Logger) Debug(v ...interface{}) { if l.shouldLog(DEBUG) { l.logger.Println(l.formatMessage(DEBUG, v...)) } } // Info logs an info message func (l *Logger) Info(v ...interface{}) { if l.shouldLog(INFO) { l.logger.Println(l.formatMessage(INFO, v...)) } } // Warn logs a warning message func (l *Logger) Warn(v ...interface{}) { if l.shouldLog(WARN) { message := l.formatMessage(WARN, v...) l.logger.Println(message) l.errorLogger.Println(message) // Also log to error file } } // Error logs an error message func (l *Logger) Error(v ...interface{}) { if l.shouldLog(ERROR) { message := l.formatMessage(ERROR, v...) l.logger.Println(message) l.errorLogger.Println(message) // Also log to error file } } // Opportunity logs a found opportunity with detailed information // This always logs regardless of level since opportunities are critical func (l *Logger) Opportunity(txHash, from, to, method, protocol string, amountIn, amountOut, minOut, profitUSD float64, additionalData map[string]interface{}) { timestamp := time.Now().Format("2006/01/02 15:04:05") // Create sanitized additional data for production sanitizedData := l.secureFilter.SanitizeForProduction(additionalData) message := fmt.Sprintf(`%s [OPPORTUNITY] 🎯 ARBITRAGE OPPORTUNITY DETECTED ├── Transaction: %s ├── From: %s → To: %s ├── Method: %s (%s) ├── Amount In: %.6f tokens ├── Amount Out: %.6f tokens ├── Min Out: %.6f tokens ├── Estimated Profit: $%.2f USD └── Additional Data: %v`, timestamp, txHash, from, to, method, protocol, amountIn, amountOut, minOut, profitUSD, sanitizedData) // Apply security filtering to the entire message filteredMessage := l.secureFilter.FilterMessage(message) l.logger.Println(filteredMessage) l.opportunityLogger.Println(filteredMessage) // Dedicated opportunity log } // OpportunitySimple logs a simple opportunity message (for backwards compatibility) func (l *Logger) OpportunitySimple(v ...interface{}) { timestamp := time.Now().Format("2006/01/02 15:04:05") message := fmt.Sprintf("%s [OPPORTUNITY] %s", timestamp, fmt.Sprint(v...)) l.logger.Println(message) l.opportunityLogger.Println(message) // Dedicated opportunity log } // Performance logs performance metrics for optimization analysis func (l *Logger) Performance(component, operation string, duration time.Duration, metadata map[string]interface{}) { timestamp := time.Now().Format("2006/01/02 15:04:05") // Add standard performance fields data := map[string]interface{}{ "component": component, "operation": operation, "duration_ms": duration.Milliseconds(), "duration_ns": duration.Nanoseconds(), "timestamp": timestamp, } // Merge with provided metadata for k, v := range metadata { data[k] = v } message := fmt.Sprintf(`%s [PERFORMANCE] 📊 %s.%s completed in %v - %v`, timestamp, component, operation, duration, data) l.performanceLogger.Println(message) // Dedicated performance log only } // Metrics logs business metrics for analysis func (l *Logger) Metrics(name string, value float64, unit string, tags map[string]string) { timestamp := time.Now().Format("2006/01/02 15:04:05") message := fmt.Sprintf(`%s [METRICS] 📈 %s: %.6f %s %v`, timestamp, name, value, unit, tags) l.performanceLogger.Println(message) // Metrics go to performance log } // Transaction logs detailed transaction information for MEV analysis func (l *Logger) Transaction(txHash, from, to, method, protocol string, gasUsed, gasPrice uint64, value float64, success bool, metadata map[string]interface{}) { timestamp := time.Now().Format("2006/01/02 15:04:05") status := "FAILED" if success { status = "SUCCESS" } // Sanitize metadata for production sanitizedMetadata := l.secureFilter.SanitizeForProduction(metadata) message := fmt.Sprintf(`%s [TRANSACTION] 💳 %s ├── Hash: %s ├── From: %s → To: %s ├── Method: %s (%s) ├── Gas Used: %d (Price: %d wei) ├── Value: %.6f ETH ├── Status: %s └── Metadata: %v`, timestamp, status, txHash, from, to, method, protocol, gasUsed, gasPrice, value, status, sanitizedMetadata) // Apply security filtering to the entire message filteredMessage := l.secureFilter.FilterMessage(message) l.transactionLogger.Println(filteredMessage) // Dedicated transaction log only } // BlockProcessing logs block processing metrics for sequencer monitoring func (l *Logger) BlockProcessing(blockNumber uint64, txCount, dexTxCount int, processingTime time.Duration) { timestamp := time.Now().Format("2006/01/02 15:04:05") message := fmt.Sprintf(`%s [BLOCK_PROCESSING] 🧱 Block %d: %d txs (%d DEX) processed in %v`, timestamp, blockNumber, txCount, dexTxCount, processingTime) l.performanceLogger.Println(message) // Block processing metrics go to performance log } // ArbitrageAnalysis logs arbitrage opportunity analysis results func (l *Logger) ArbitrageAnalysis(poolA, poolB, tokenPair string, priceA, priceB, priceDiff, estimatedProfit float64, feasible bool) { timestamp := time.Now().Format("2006/01/02 15:04:05") status := "REJECTED" if feasible { status = "VIABLE" } message := fmt.Sprintf(`%s [ARBITRAGE_ANALYSIS] 🔍 %s %s ├── Pool A: %s (Price: %.6f) ├── Pool B: %s (Price: %.6f) ├── Price Difference: %.4f%% ├── Estimated Profit: $%.2f └── Status: %s`, timestamp, status, tokenPair, poolA, priceA, poolB, priceB, priceDiff*100, estimatedProfit, status) // Apply security filtering to protect sensitive pricing data filteredMessage := l.secureFilter.FilterMessage(message) l.opportunityLogger.Println(filteredMessage) // Arbitrage analysis goes to opportunity log } // RPC logs RPC call metrics for endpoint optimization func (l *Logger) RPC(endpoint, method string, duration time.Duration, success bool, errorMsg string) { timestamp := time.Now().Format("2006/01/02 15:04:05") status := "SUCCESS" if !success { status = "FAILED" } message := fmt.Sprintf(`%s [RPC] 🌐 %s %s.%s in %v`, timestamp, status, endpoint, method, duration) if !success && errorMsg != "" { message += fmt.Sprintf(" - Error: %s", errorMsg) } l.performanceLogger.Println(message) // RPC metrics go to performance log } // SwapAnalysis logs swap event analysis with security filtering func (l *Logger) SwapAnalysis(tokenIn, tokenOut string, amountIn, amountOut float64, protocol, poolAddr string, metadata map[string]interface{}) { timestamp := time.Now().Format("2006/01/02 15:04:05") // Sanitize metadata for production sanitizedMetadata := l.secureFilter.SanitizeForProduction(metadata) message := fmt.Sprintf(`%s [SWAP_ANALYSIS] 🔄 %s → %s ├── Amount In: %.6f %s ├── Amount Out: %.6f %s ├── Protocol: %s ├── Pool: %s └── Metadata: %v`, timestamp, tokenIn, tokenOut, amountIn, tokenIn, amountOut, tokenOut, protocol, poolAddr, sanitizedMetadata) // Apply security filtering to the entire message filteredMessage := l.secureFilter.FilterMessage(message) l.transactionLogger.Println(filteredMessage) // Dedicated transaction log }