package shell import ( "fmt" "strings" "time" "github.com/lucasdataproyects/devfactory/core" dfshell "github.com/lucasdataproyects/devfactory/shell" pcore "github.com/lucasdataproyects/parallel-executor/core" ) // Logger escribe logs de la ejecución paralela a disco. type Logger struct { logsDir string sessionID string } // NewLogger crea un logger para la sesión actual. func NewLogger(repoRoot string) core.Result[*Logger] { sessionID := time.Now().Format("20060102-150405") logsDir := repoRoot + "/logs" result := dfshell.MkdirAll(logsDir) if result.IsErr() { return core.Err[*Logger](result.Error()) } return core.Ok(&Logger{ logsDir: logsDir, sessionID: sessionID, }) } // LogIssueStart registra el inicio de ejecución de una issue. func (l *Logger) LogIssueStart(issue pcore.Issue) { msg := fmt.Sprintf("[%s] START Issue #%04d - %s\n", time.Now().Format("15:04:05"), issue.Number, issue.Title) l.appendToSession(msg) } // LogIssueResult registra el resultado de una issue. func (l *Logger) LogIssueResult(result pcore.ExecutionResult) { status := "SUCCESS" if !result.Success { status = "FAILED" } msg := fmt.Sprintf("[%s] %s Issue #%04d - %s (duration: %s)", time.Now().Format("15:04:05"), status, result.Issue.Number, result.Issue.Title, result.Duration) if result.Error != "" { msg += "\n Error: " + result.Error } msg += "\n" l.appendToSession(msg) // Log individual por issue issueLogFile := fmt.Sprintf("%s/issue-%04d-%s.log", l.logsDir, result.Issue.Number, l.sessionID) content := fmt.Sprintf("Issue #%04d - %s\nStatus: %s\nDuration: %s\n", result.Issue.Number, result.Issue.Title, status, result.Duration) if result.Error != "" { content += "Error:\n" + result.Error + "\n" } dfshell.WriteString(issueLogFile, content) } // WriteSummary escribe el resumen consolidado. func (l *Logger) WriteSummary(results []pcore.ExecutionResult) core.Result[string] { summaryFile := fmt.Sprintf("%s/summary-%s.txt", l.logsDir, l.sessionID) var b strings.Builder b.WriteString("=" + strings.Repeat("=", 59) + "\n") b.WriteString(fmt.Sprintf(" Parallel Execution Summary — %s\n", l.sessionID)) b.WriteString("=" + strings.Repeat("=", 59) + "\n\n") succeeded := 0 failed := 0 for _, r := range results { if r.Success { succeeded++ } else { failed++ } } b.WriteString(fmt.Sprintf("Total: %d\n", len(results))) b.WriteString(fmt.Sprintf("Succeeded: %d\n", succeeded)) b.WriteString(fmt.Sprintf("Failed: %d\n\n", failed)) b.WriteString("Results:\n") b.WriteString(strings.Repeat("-", 60) + "\n") for _, r := range results { status := "✓" if !r.Success { status = "✗" } b.WriteString(fmt.Sprintf(" %s #%04d %-30s %s\n", status, r.Issue.Number, r.Issue.Title, r.Duration)) if r.Error != "" { b.WriteString(fmt.Sprintf(" Error: %s\n", truncate(r.Error, 80))) } } b.WriteString(strings.Repeat("-", 60) + "\n") writeResult := dfshell.WriteString(summaryFile, b.String()) if writeResult.IsErr() { return core.Err[string](writeResult.Error()) } return core.Ok(summaryFile) } // SessionLogFile retorna la ruta del log de sesión. func (l *Logger) SessionLogFile() string { return fmt.Sprintf("%s/parallel-execution-%s.log", l.logsDir, l.sessionID) } func (l *Logger) appendToSession(msg string) { logFile := l.SessionLogFile() dfshell.AppendFile(logFile, []byte(msg)) } func truncate(s string, max int) string { if len(s) <= max { return s } return s[:max-3] + "..." }