package host import ( "fmt" "time" "github.com/carlosframework/platform/internal/applog" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/runs" ) // A run's two lines in `carlos logs` (amadan #325): when it started and who // asked for it, then how it ended. The output stays in the run's chunks and // the argv in the audit log; neither is ever written here, since a log // reader is not necessarily someone the command's arguments were meant for. // // Both lines are info. The Errors and Availability views read the platform // stream at warn and above, and a command that failed says nothing about // whether the instance could serve. const runEventLevel = applog.LevelInfo // runStartedLine is the line for a run whose unit has started. A service's // run names the service, and the person it named only as its statement // (service runs spec ยง2). func runStartedLine(req runs.Request) string { if by := req.ServiceBy(); by != "" { return fmt.Sprintf("run %s started %s", req.ID, by) } return fmt.Sprintf("run %s started by %s", req.ID, req.By) } // runEndedLine is the line for a run's result, whatever ended it. started // says whether RunCmd handed back a running unit: a result written before // that (a refusal, a cancel or a timeout during the wake or the staging, a // stop that overlapped the launch) is a run that did not start, whatever // its fields say. A failed start is the exception: StartCmd can answer an // error for a unit the control service did start (a lost response), and // RunCmd then stops it and hands back nothing, so the line hedges as the // CLI's does rather than say it never ran. A cancel or a deadline that // lands during that same start is written as cancelled or timed out with // no handle, and its line says "before it started", as the CLI says of the // same result: the result does not record whether the start was reached, // and the line restates the result, never more. func runEndedLine(id string, res runs.Result, started bool) string { took := res.Ended.Sub(res.Started).Round(time.Second) switch { case res.Refused != "": return fmt.Sprintf("run %s did not start: %s", id, runRefusedWords(res)) case res.Interrupted != "" && !started && res.Interrupted != runs.InterruptedStartFailed: return fmt.Sprintf("run %s did not start: %s", id, res.Interrupted) case res.Cancelled && !started: return fmt.Sprintf("run %s was cancelled before it started", id) case res.Cancelled: return fmt.Sprintf("run %s was cancelled after %s", id, took) case res.TimedOut && !started: return fmt.Sprintf("run %s timed out before it started", id) case res.TimedOut: return fmt.Sprintf("run %s timed out after %s", id, took) case res.Interrupted != "": return fmt.Sprintf("run %s was interrupted: %s. it may have done part of its work", id, res.Interrupted) case res.Signal != "": return fmt.Sprintf("run %s was ended by signal %s after %s", id, res.Signal, took) } return fmt.Sprintf("run %s ended with exit %d after %s", id, res.Exit, took) } // runRefusedWords is why the box did not start the run. An unknown code // renders as itself, as the CLI's does. func runRefusedWords(res runs.Result) string { switch res.Refused { case runs.RefusedFlat: return "the box is not on the nested layout yet" case runs.RefusedClaim: return "the box has host claims armed" case runs.RefusedWake: return fmt.Sprintf("the instance did not wake. %s", res.Detail) case runs.RefusedIncarnation: return "the instance was deleted and created again after the run was requested" case runs.RefusedImport: return "the instance is an import that is not committed yet" } return res.Refused } // runEvent publishes one of a run's lines against the record's instance. // A box with no log store has no sink, and emits nothing. func (w *runsWorker) runEvent(rec provision.Record, line string) { if w.events == nil { return } w.events.Event(rec.Account, rec.App, rec.Host, runEventLevel, line) }