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

@@ -33,13 +33,14 @@ func (blogPostsPlugin) Routes(r pact.Router) error {
return nil
}
// accessLogRouter assembles the blog plugin with http.access_log set to on
// and a published JSON logger writing into the returned buffer.
// accessLogRouter assembles the blog plugin with http.access_log left at
// its default (on) or set to false, and a published JSON logger writing
// into the returned buffer.
func accessLogRouter(t *testing.T, on bool) (http.Handler, *bytes.Buffer) {
t.Helper()
y := "body_limits:\n default_bytes: 1024\n upload_bytes: 1024\n"
if on {
y += "access_log: true\n"
if !on {
y += "access_log: false\n"
}
app := backpack.New(writeHTTPConfig(t, y))
var buf bytes.Buffer
@@ -76,18 +77,23 @@ func TestAccessLogSmoke(t *testing.T) {
h, buf := accessLogRouter(t, true)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/blog/posts/7?token=secret", nil))
req := httptest.NewRequest(http.MethodGet, "/blog/posts/7?token=secret", nil)
req.Header.Set("X-Request-Id", " req-42 ")
h.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("GET /blog/posts/7 = %d", rec.Code)
}
recs := accessRecords(t, buf)
if len(recs) != 1 {
t.Fatalf("records = %d, want 1:\n%s", len(recs), buf.String())
t.Fatalf("records = %d, want 1 (the request log is on by default):\n%s", len(recs), buf.String())
}
r := recs[0]
if r["method"] != "GET" || r["pattern"] != "GET /blog/posts/{id}" || r["status"] != float64(200) || r["level"] != "INFO" {
t.Fatalf("matched record = %v", r)
}
if r["request_id"] != "req-42" {
t.Fatalf("request_id = %v, want the trimmed X-Request-Id", r["request_id"])
}
if _, ok := r["duration_ms"].(float64); !ok {
t.Fatalf("matched record has no duration_ms: %v", r)
}
@@ -112,6 +118,17 @@ func TestAccessLogSmoke(t *testing.T) {
if r["pattern"] != "" || r["status"] != float64(404) {
t.Fatalf("unmatched record = %v", r)
}
if _, ok := r["request_id"]; ok {
t.Fatalf("a request without X-Request-Id has a request_id: %v", r)
}
buf.Reset()
long := httptest.NewRequest(http.MethodGet, "/blog/posts/7", nil)
long.Header.Set("X-Request-Id", strings.Repeat("x", 300))
h.ServeHTTP(httptest.NewRecorder(), long)
if recs = accessRecords(t, buf); len(recs) != 1 || len(recs[0]["request_id"].(string)) != errorPageMaxRequestID {
t.Fatalf("a long X-Request-Id is not cut to %d bytes: %v", errorPageMaxRequestID, recs)
}
if _, ok := r["path"]; ok {
t.Fatalf("unmatched record carries path: %v", r)
}
@@ -242,6 +259,8 @@ func accessLogEdgesRouter(t *testing.T, on, locale bool, flushErr *error) (http.
y := "body_limits:\n default_bytes: 1024\n upload_bytes: 1024\n"
if on {
y += "access_log: true\n"
} else {
y += "access_log: false\n"
}
app := backpack.New(writeHTTPConfig(t, y))
var buf bytes.Buffer