feat(surf): serve startup summary, shutdown report and http request log

- serve prints a startup summary on a terminal (version, environment, URL,
  admin, database host and name, pending migrations, plugins, drivers) and
  logs it as a 'startup summary' record plus 'listening on <addr>'
- on SIGINT/SIGTERM serve drains, prints and logs a shutdown report and
  returns nil (exit 0), or the drain errors (exit 1)
- the request log is on by default under diary component http with
  request_id; the route pattern, never the path, is logged
- lagoon.PendingMigrations, conga.Worker.Stats, diary.HideOnTTY,
  bonfire.IsTerminal and bonfire.Details back the summary and report
This commit is contained in:
Jakub Zych
2026-10-21 12:59:07 +02:00
parent f0ee26f39f
commit c38dc1fe48
23 changed files with 1333 additions and 94 deletions

View File

@@ -3,6 +3,7 @@ package bonfire
import (
"context"
"errors"
"strings"
tea "charm.land/bubbletea/v2"
)
@@ -13,12 +14,15 @@ import (
type consoleState interface {
consoleGlobals() Globals
consoleInteractive() bool
consoleTerminal() bool
}
func (c *console) consoleGlobals() Globals { return c.globals }
func (c *console) consoleInteractive() bool { return c.canPrompt() }
func (c *console) consoleTerminal() bool { return c.tty }
// GlobalsOf returns the global options of the command out belongs to, or
// the zero Globals when out is not a bonfire console.
func GlobalsOf(out Output) Globals {
@@ -48,6 +52,66 @@ func Interactive(out Output) bool {
return false
}
// IsTerminal reports whether out writes to a terminal (its stdout is one).
// It is false for other Output implementations and inside bonfire.Call.
// Long-running commands use it to choose between a styled summary block for
// a person and log records for journald.
func IsTerminal(out Output) bool {
if s, ok := out.(consoleState); ok {
return s.consoleTerminal()
}
return false
}
// Detail is one line of a Details block: a label and its value. Warn
// prints the value in the warning color.
type Detail struct {
Label string
Value string
Warn bool
}
// Details prints title (when not empty) and rows as aligned label/value
// lines in the console palette: muted labels padded to the longest one,
// values in the default color or, with Warn, in the warning color. It
// prints nothing under -q. serve, queue:work and summer dev use it for
// their startup summaries and shutdown reports.
func Details(out Output, title string, rows []Detail) {
if IsQuiet(out) {
return
}
width := 0
for _, r := range rows {
width = max(width, len([]rune(r.Label)))
}
c, styled := out.(*console)
var b strings.Builder
if title != "" {
if styled {
b.WriteString(c.palette.Header.Render(title))
} else {
b.WriteString(title)
}
b.WriteByte('\n')
}
for _, r := range rows {
label := r.Label + strings.Repeat(" ", width-len([]rune(r.Label)))
value := r.Value
if styled {
label = c.palette.Muted.Render(label)
if r.Warn {
value = c.palette.Warning.Render(value)
}
}
b.WriteString(" " + label + " " + value + "\n")
}
if styled {
c.emit(c.out, b.String())
return
}
out.Printf("%s", b.String())
}
// ConfirmProceed asks question before a destructive step, defaulting to
// no, only when out is Interactive. Off a terminal, with -n or -q and in
// bonfire.Call it returns true without asking, so deploys over SSH and