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 simple logger wrapper type Logger struct { logger *log.Logger level LogLevel 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 } } // New creates a new logger func New(level string, format string, file string) *Logger { // Determine output destination var output *os.File if file != "" { f, err := os.OpenFile(file, 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", file, err) output = os.Stdout } else { output = f } } else { output = os.Stdout } // Create the logger with custom format logger := log.New(output, "", 0) // No flags, we'll format ourselves logLevel := parseLogLevel(level) return &Logger{ logger: logger, level: logLevel, 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) { l.logger.Println(l.formatMessage(WARN, v...)) } } // Error logs an error message func (l *Logger) Error(v ...interface{}) { if l.shouldLog(ERROR) { l.logger.Println(l.formatMessage(ERROR, v...)) } } // 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") 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, additionalData) l.logger.Println(message) } // 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.Sprint(v...) l.logger.Printf("%s [OPPORTUNITY] %s", timestamp, message) }