Commit 9aad8f0

Eric Bower  ·  2026-05-06 21:02:52 -0400 EDT
parent 643168c
style: use emoji based logs
1 files changed,  +61, -36
+61, -36
......@@ -42,6 +42,7 @@ type Cfg struct {
4242 NewWorkspace WorkspaceFactory
4343 StatusOutput io.Writer // where status JSONL is written (default: os.Stdout)
4444 StatusFilter string // "terminal" (default) or "all"
45+ HumanOutput bool // human-readable output instead of JSONL / slog
4546 }
4647
4748 type Event struct {
......@@ -65,7 +66,9 @@ func NewCfg() *Cfg {
6566 flag.StringVar(&logLevel, "log-level", "info", "log level: debug, info, warn, error")
6667 flag.BoolVar(&structured, "structured", false, "use structured key=value log output")
6768 var statusFilter string
69+ var human bool
6870 flag.StringVar(&statusFilter, "status-filter", "terminal", "status output filter: terminal (default) or all")
71+ flag.BoolVar(&human, "human", false, "human-readable output (default: JSONL / slog)")
6972 flag.Parse()
7073
7174 logger := newLogger("ci", logLevel, structured)
......@@ -81,6 +84,7 @@ func NewCfg() *Cfg {
8184 Event: event,
8285 MonitorInterval: monitorInterval,
8386 StatusFilter: statusFilter,
87+ HumanOutput: human,
8488 }
8589 }
8690
......@@ -182,7 +186,8 @@ FLAGS
182186 -monitor-interval <dur> Interval for monitoring zmx sessions (default: 5s)
183187 -log-level <level> Log level: debug, info, warn, error (default: info)
184188 -structured Use structured key=value log output
185- -status-filter <filter> Status output filter: terminal (default) or all`)
189+ -status-filter <filter> Status output filter: terminal (default) or all
190+ -human Human-readable output instead of JSONL / slog`)
186191 }
187192
188193 func RunRunner(cfg *Cfg) error {
......@@ -219,8 +224,6 @@ func RunRunner(cfg *Cfg) error {
219224 return fmt.Errorf("event missing required field: workspace")
220225 }
221226
222- cfg.Logger.Info("received event", "type", eventData.Type, "repo", eventData.Name, "workspace", eventData.Workspace)
223-
224227 return eventHandler(cfg, &eventData)
225228 }
226229
......@@ -348,14 +351,13 @@ func (eng *JobEngine) getManifest() (string, error) {
348351
349352 func eventHandler(cfg *Cfg, eventData *Event) error {
350353 log := cfg.Logger.With("repo", eventData.Name, "type", eventData.Type)
351- log.Info("processing event", "workspace", eventData.Workspace)
352354
353355 // Cancel any existing job for this repo before starting a new one
354356 cancelRunningJobs(cfg, log, eventData.Name)
355357
356358 jobID := generateJobID(eventData.Name, eventData.Workspace)
357359 log = log.With("job_id", jobID)
358- log.Info("starting job", "session_prefix", fmt.Sprintf("ci.%s.%s", eventData.Name, jobID))
360+ fmt.Fprintf(os.Stdout, "🚀 starting job ci.%s.%s\n", eventData.Name, jobID)
359361
360362 wk := cfg.NewWorkspace(cfg, log, eventData.Workspace)
361363 eng := &JobEngine{
......@@ -371,11 +373,11 @@ func eventHandler(cfg *Cfg, eventData *Event) error {
371373 }
372374 }()
373375
374- log.Info("cloning workspace", "source", eventData.Workspace)
376+ fmt.Fprint(os.Stdout, "📦 syncing workspace...\n")
375377 if err := eng.Setup(); err != nil {
376378 return fmt.Errorf("setup: %w", err)
377379 }
378- log.Info("workspace ready", "dir", eng.Wk.GetDir())
380+ fmt.Fprintln(os.Stdout, "✅ workspace ready")
379381
380382 // Store the event in the artifact directory so the monitor can access it
381383 eventBytes, _ := json.Marshal(eventData)
......@@ -388,13 +390,13 @@ func eventHandler(cfg *Cfg, eventData *Event) error {
388390 }
389391 }
390392
391- log.Info("launching job sessions")
393+ fmt.Fprint(os.Stdout, "🏃 launching sessions...\n")
392394 if err := eng.Run(); err != nil {
393395 return fmt.Errorf("run: %w", err)
394396 }
395397
396- log.Info("job launched successfully — monitor will track progress", "artifact_dir", cfg.ArtifactDir, "artifact_dest", eventData.ArtifactDest)
397- log.Info("follow the runner live", "command", fmt.Sprintf("zmx tail ci.%s.%s.runner", eventData.Name, jobID))
398+ fmt.Fprintln(os.Stdout, "✅ job launched")
399+ fmt.Fprintf(os.Stdout, " follow: zmx tail ci.%s.%s.runner\n", eventData.Name, jobID)
398400 return nil
399401 }
400402
......@@ -517,42 +519,55 @@ func monitorTick(cfg *Cfg, log *slog.Logger, output io.Writer) error {
517519 log.Debug("job completed, publishing final status", "sessions", len(group))
518520 exitCode, status := resolveJobExitCode(group)
519521 log.Info("job finished", "status", status, "exit_code", exitCode)
520- payload := StatusPayload{
521- Name: name,
522- JobID: jobID,
523- Status: status,
524- ExitCode: &exitCode,
525- Duration: duration,
526- StartedAt: startedAt,
527- EndedAt: endedAt,
528- ArtifactURL: artifactURL,
529- SessionCount: len(group),
530- Sessions: group,
531- }
532- if err := publishStatus(output, payload); err != nil {
533- log.Error("publish final status", "err", err)
534- }
535- // Write sentinel so we don't re-publish on subsequent ticks
536- if err := os.WriteFile(sentinel, []byte(status), 0644); err != nil {
537- log.Error("write published sentinel", "err", err)
538- }
539- } else {
540- log.Debug("job still running", "sessions", len(group))
541- // Publish running status only when filter is "all"
542- if cfg.StatusFilter != "terminal" {
522+
523+ if cfg.HumanOutput {
524+ icon := map[string]string{"success": "✅", "failed": "❌"}[status]
525+ fmt.Fprintf(output, "[%d/%d] %s %s: %s (%s)\n",
526+ countCompleted(group), len(group), icon, status, name, duration)
527+ } else {
543528 payload := StatusPayload{
544529 Name: name,
545530 JobID: jobID,
546- Status: "running",
547- ExitCode: nil,
531+ Status: status,
532+ ExitCode: &exitCode,
548533 Duration: duration,
549534 StartedAt: startedAt,
535+ EndedAt: endedAt,
550536 ArtifactURL: artifactURL,
551537 SessionCount: len(group),
552538 Sessions: group,
553539 }
554540 if err := publishStatus(output, payload); err != nil {
555- log.Error("publish status", "err", err)
541+ log.Error("publish final status", "err", err)
542+ }
543+ }
544+ // Write sentinel so we don't re-publish on subsequent ticks
545+ if err := os.WriteFile(sentinel, []byte(status), 0644); err != nil {
546+ log.Error("write published sentinel", "err", err)
547+ }
548+ } else {
549+ log.Debug("job still running", "sessions", len(group))
550+ // Publish running status only when filter is "all"
551+ if cfg.StatusFilter != "terminal" {
552+ if cfg.HumanOutput {
553+ completed := countCompleted(group)
554+ fmt.Fprintf(output, "[%d/%d] 🚀 running: %s (%s)\n",
555+ completed, len(group), name, duration)
556+ } else {
557+ payload := StatusPayload{
558+ Name: name,
559+ JobID: jobID,
560+ Status: "running",
561+ ExitCode: nil,
562+ Duration: duration,
563+ StartedAt: startedAt,
564+ ArtifactURL: artifactURL,
565+ SessionCount: len(group),
566+ Sessions: group,
567+ }
568+ if err := publishStatus(output, payload); err != nil {
569+ log.Error("publish status", "err", err)
570+ }
556571 }
557572 }
558573 }
......@@ -867,6 +882,16 @@ func allCompleted(sessions []SessionInfo) bool {
867882 return true
868883 }
869884
885+func countCompleted(sessions []SessionInfo) int {
886+ count := 0
887+ for _, s := range sessions {
888+ if s.Ended != "" {
889+ count++
890+ }
891+ }
892+ return count
893+}
894+
870895 func runCmd(cmd *exec.Cmd, log *slog.Logger) error {
871896 stdout, err := cmd.StdoutPipe()
872897 if err != nil {