package main import ( "encoding/json" "os" "path/filepath" "strings" "sync" "time" ) // Event is one line in the flight log: every triage, flight and verdict. type Event struct { Time time.Time `json:"ts"` Kind string `json:"kind"` Requester string `json:"requester,omitempty"` Item string `json:"item,omitempty"` Crow string `json:"crow,omitempty"` Mode string `json:"mode,omitempty"` TaskType string `json:"task_type,omitempty"` Model string `json:"model,omitempty"` // which model actually answered (routers vary) Tokens int `json:"tokens,omitempty"` Ms int64 `json:"ms,omitempty"` Verdict string `json:"verdict,omitempty"` Output string `json:"output,omitempty"` Error string `json:"error,omitempty"` Note string `json:"note,omitempty"` Prob any `json:"probs,omitempty"` // question probabilities, when a call returns them (calibration data) } type FlightLog struct { mu sync.Mutex f *os.File } func OpenFlightLog(path string) (*FlightLog, error) { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return nil, err } f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) if err != nil { return nil, err } return &FlightLog{f: f}, nil } func (l *FlightLog) Write(e Event) { if l == nil { return // no log configured (tests, embedded use) } l.mu.Lock() defer l.mu.Unlock() e.Time = time.Now() b, _ := json.Marshal(e) l.f.Write(append(b, '\n')) } func (l *FlightLog) Close() error { return l.f.Close() } // ReadEvents parses a flight log file back into events, in the order written. func ReadEvents(path string) ([]Event, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } var out []Event for _, line := range strings.Split(string(data), "\n") { if strings.TrimSpace(line) == "" { continue } var e Event if err := json.Unmarshal([]byte(line), &e); err != nil { return nil, err } out = append(out, e) } return out, nil }