Monorepo for Tangled — https://tangled.org
at spindle-log-err 141 lines 3.2 kB view raw
1package models 2 3import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "strings" 10) 11 12type WorkflowLogger struct { 13 file *os.File 14 encoder *json.Encoder 15} 16 17func NewWorkflowLogger(baseDir string, wid WorkflowId) (*WorkflowLogger, error) { 18 path := LogFilePath(baseDir, wid) 19 20 file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) 21 if err != nil { 22 return nil, fmt.Errorf("creating log file: %w", err) 23 } 24 25 return &WorkflowLogger{ 26 file: file, 27 encoder: json.NewEncoder(file), 28 }, nil 29} 30 31func LogFilePath(baseDir string, workflowID WorkflowId) string { 32 logFilePath := filepath.Join(baseDir, fmt.Sprintf("%s.log", workflowID.String())) 33 return logFilePath 34} 35 36func (l *WorkflowLogger) Close() error { 37 return l.file.Close() 38} 39 40// WriteSetupError writes a setup error (e.g., image pull failure) to the log. 41func (l *WorkflowLogger) WriteSetupError(msg string) error { 42 step := SimpleStep{N: "Setup", K: StepKindSystem} 43 44 // Start the step 45 if err := l.encoder.Encode(NewControlLogLine(0, step, StepStatusStart)); err != nil { 46 return err 47 } 48 49 // Write the error as data 50 if err := l.encoder.Encode(NewDataLogLine(0, msg, "stderr")); err != nil { 51 return err 52 } 53 54 // End the step 55 return l.encoder.Encode(NewControlLogLine(0, step, StepStatusEnd)) 56} 57 58func (l *WorkflowLogger) DataWriter(idx int, stream string) io.Writer { 59 return &dataWriter{ 60 logger: l, 61 idx: idx, 62 stream: stream, 63 } 64} 65 66func (l *WorkflowLogger) ControlWriter(idx int, step Step, stepStatus StepStatus) io.Writer { 67 return &controlWriter{ 68 logger: l, 69 idx: idx, 70 step: step, 71 stepStatus: stepStatus, 72 } 73} 74 75type dataWriter struct { 76 logger *WorkflowLogger 77 idx int 78 stream string 79} 80 81func (w *dataWriter) Write(p []byte) (int, error) { 82 line := strings.TrimRight(string(p), "\r\n") 83 entry := NewDataLogLine(w.idx, line, w.stream) 84 if err := w.logger.encoder.Encode(entry); err != nil { 85 return 0, err 86 } 87 return len(p), nil 88} 89 90type controlWriter struct { 91 logger *WorkflowLogger 92 idx int 93 step Step 94 stepStatus StepStatus 95} 96 97func (w *controlWriter) Write(_ []byte) (int, error) { 98 entry := NewControlLogLine(w.idx, w.step, w.stepStatus) 99 if err := w.logger.encoder.Encode(entry); err != nil { 100 return 0, err 101 } 102 return len(w.step.Name()), nil 103} 104 105// registryError represents the error format returned by Docker registries 106type registryError struct { 107 Errors []struct { 108 Code string `json:"code"` 109 Message string `json:"message"` 110 } `json:"errors"` 111} 112 113// ParseSetupError extracts a user-friendly message from setup errors. 114// It handles Docker registry errors (like nixery) that contain JSON payloads. 115// Falls back to the original error message if parsing fails. 116func ParseSetupError(err error) string { 117 if err == nil { 118 return "" 119 } 120 121 errStr := err.Error() 122 123 // Look for JSON payload after "unknown: " (Docker registry error format) 124 idx := strings.Index(errStr, "unknown: ") 125 if idx == -1 { 126 return errStr 127 } 128 129 jsonStr := errStr[idx+len("unknown: "):] 130 131 var regErr registryError 132 if jsonErr := json.Unmarshal([]byte(jsonStr), &regErr); jsonErr != nil { 133 return errStr 134 } 135 136 if len(regErr.Errors) > 0 && regErr.Errors[0].Message != "" { 137 return regErr.Errors[0].Message 138 } 139 140 return errStr 141}