package parsers import ( "context" "encoding/json" "fmt" "os" "path/filepath" "sync" "time" "github.com/your-org/mev-bot/pkg/types" ) // SwapLogger logs detected swaps to files for testing and accuracy verification type SwapLogger struct { logDir string mu sync.Mutex logger types.Logger } // SwapLogEntry represents a logged swap event with metadata type SwapLogEntry struct { Timestamp time.Time `json:"timestamp"` SwapEvent *types.SwapEvent `json:"swap_event"` RawLogData string `json:"raw_log_data"` // Hex-encoded log data RawLogTopics []string `json:"raw_log_topics"` // Hex-encoded topics Parser string `json:"parser"` // Which parser detected this } // NewSwapLogger creates a new swap logger func NewSwapLogger(logDir string, logger types.Logger) (*SwapLogger, error) { // Create log directory if it doesn't exist if err := os.MkdirAll(logDir, 0755); err != nil { return nil, fmt.Errorf("failed to create log directory: %w", err) } return &SwapLogger{ logDir: logDir, logger: logger, }, nil } // LogSwap logs a detected swap event func (s *SwapLogger) LogSwap(ctx context.Context, event *types.SwapEvent, rawLogData []byte, rawLogTopics []string, parser string) error { s.mu.Lock() defer s.mu.Unlock() // Create log entry entry := SwapLogEntry{ Timestamp: time.Now(), SwapEvent: event, RawLogData: fmt.Sprintf("0x%x", rawLogData), RawLogTopics: rawLogTopics, Parser: parser, } // Marshal to JSON data, err := json.MarshalIndent(entry, "", " ") if err != nil { return fmt.Errorf("failed to marshal log entry: %w", err) } // Create filename based on timestamp and tx hash filename := fmt.Sprintf("%s_%s.json", time.Now().Format("2006-01-02_15-04-05"), event.TxHash.Hex()[2:10], // First 8 chars of tx hash ) // Write to file logPath := filepath.Join(s.logDir, filename) if err := os.WriteFile(logPath, data, 0644); err != nil { return fmt.Errorf("failed to write log file: %w", err) } s.logger.Debug("logged swap event", "txHash", event.TxHash.Hex(), "protocol", event.Protocol, "parser", parser, "logPath", logPath, ) return nil } // LogSwapBatch logs multiple swap events from the same transaction func (s *SwapLogger) LogSwapBatch(ctx context.Context, events []*types.SwapEvent, parser string) error { if len(events) == 0 { return nil } s.mu.Lock() defer s.mu.Unlock() // Create batch entry type BatchEntry struct { Timestamp time.Time `json:"timestamp"` TxHash string `json:"tx_hash"` Parser string `json:"parser"` SwapCount int `json:"swap_count"` Swaps []*types.SwapEvent `json:"swaps"` } entry := BatchEntry{ Timestamp: time.Now(), TxHash: events[0].TxHash.Hex(), Parser: parser, SwapCount: len(events), Swaps: events, } // Marshal to JSON data, err := json.MarshalIndent(entry, "", " ") if err != nil { return fmt.Errorf("failed to marshal batch entry: %w", err) } // Create filename filename := fmt.Sprintf("%s_%s_batch.json", time.Now().Format("2006-01-02_15-04-05"), events[0].TxHash.Hex()[2:10], ) // Write to file logPath := filepath.Join(s.logDir, filename) if err := os.WriteFile(logPath, data, 0644); err != nil { return fmt.Errorf("failed to write batch log file: %w", err) } s.logger.Debug("logged swap batch", "txHash", events[0].TxHash.Hex(), "swapCount", len(events), "parser", parser, "logPath", logPath, ) return nil } // GetLogDir returns the log directory path func (s *SwapLogger) GetLogDir() string { return s.logDir } // LoadSwapLog loads a swap log entry from a file func LoadSwapLog(filePath string) (*SwapLogEntry, error) { data, err := os.ReadFile(filePath) if err != nil { return nil, fmt.Errorf("failed to read log file: %w", err) } var entry SwapLogEntry if err := json.Unmarshal(data, &entry); err != nil { return nil, fmt.Errorf("failed to unmarshal log entry: %w", err) } return &entry, nil } // ListSwapLogs returns all swap log files in the log directory func (s *SwapLogger) ListSwapLogs() ([]string, error) { s.mu.Lock() defer s.mu.Unlock() entries, err := os.ReadDir(s.logDir) if err != nil { return nil, fmt.Errorf("failed to read log directory: %w", err) } var logFiles []string for _, entry := range entries { if !entry.IsDir() && filepath.Ext(entry.Name()) == ".json" { logFiles = append(logFiles, filepath.Join(s.logDir, entry.Name())) } } return logFiles, nil } // CleanOldLogs removes log files older than the specified duration func (s *SwapLogger) CleanOldLogs(maxAge time.Duration) (int, error) { s.mu.Lock() defer s.mu.Unlock() entries, err := os.ReadDir(s.logDir) if err != nil { return 0, fmt.Errorf("failed to read log directory: %w", err) } cutoff := time.Now().Add(-maxAge) removed := 0 for _, entry := range entries { if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" { continue } info, err := entry.Info() if err != nil { s.logger.Warn("failed to get file info", "file", entry.Name(), "error", err) continue } if info.ModTime().Before(cutoff) { filePath := filepath.Join(s.logDir, entry.Name()) if err := os.Remove(filePath); err != nil { s.logger.Warn("failed to remove old log file", "file", filePath, "error", err) continue } removed++ } } s.logger.Info("cleaned old swap logs", "removed", removed, "maxAge", maxAge) return removed, nil }