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:
@@ -8,11 +8,16 @@ import (
|
||||
"time"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/diary"
|
||||
)
|
||||
|
||||
// accessLogKey is the config key that turns the request log on.
|
||||
// accessLogKey is the config key that turns the request log off (false).
|
||||
const accessLogKey = "http.access_log"
|
||||
|
||||
// httpLog is the request log's component logger (D-23): silence it with
|
||||
// log.components.http: warn or http.access_log: false.
|
||||
var httpLog = diary.For("http")
|
||||
|
||||
// accessLogMessage is the message of every request log record.
|
||||
const accessLogMessage = "http request"
|
||||
|
||||
@@ -34,14 +39,19 @@ type accessEntryKey struct{}
|
||||
// had gone away, when the error came from that cancellation.
|
||||
const statusClientClosedRequest = 499
|
||||
|
||||
// accessLogEnabled reports whether http.access_log is true.
|
||||
// accessLogEnabled reports whether the request log is on: it is unless
|
||||
// http.access_log is set to false.
|
||||
func accessLogEnabled(app *backpack.App) bool {
|
||||
return app != nil && app.Config != nil && app.Config.Bool(accessLogKey)
|
||||
if app == nil || app.Config == nil || !app.Config.Has(accessLogKey) {
|
||||
return true
|
||||
}
|
||||
return app.Config.Bool(accessLogKey)
|
||||
}
|
||||
|
||||
// accessLog logs one Info record "http request" per request after it is
|
||||
// served, with method, pattern (the matched route, "" when none matched),
|
||||
// status and duration_ms (whole milliseconds). The URL path is never
|
||||
// status, duration_ms (whole milliseconds) and request_id (the X-Request-Id
|
||||
// header trimmed and cut to 128 bytes, left out when empty). The URL path is never
|
||||
// logged, not even for a request that matched no route: a ServeMux 405 for
|
||||
// a token route reached with another method, or a 404 for its trailing-slash
|
||||
// variant, would otherwise carry the token, because share and invitation
|
||||
@@ -55,9 +65,10 @@ func accessLogEnabled(app *backpack.App) bool {
|
||||
// failed because of the cancellation, and no client received the error. A
|
||||
// 5xx written before the client went away keeps its status, and a recovered
|
||||
// panic is always 500, so a search for 5xx records still finds every real
|
||||
// server error. The logger is the app's published *slog.Logger, else
|
||||
// slog.Default (which the generated main wraps in the redacting sunscreen
|
||||
// handler).
|
||||
// server error. The logger is the app's published *slog.Logger, else the
|
||||
// diary component logger http, whose channels all redact through sunscreen.
|
||||
// The route pattern, never the raw path, is what D-23 settled on: the log
|
||||
// is on by default, so it must be safe to keep in journald and daily files.
|
||||
func accessLog(app *backpack.App, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
start := time.Now()
|
||||
@@ -77,6 +88,9 @@ func accessLog(app *backpack.App, next http.Handler) http.Handler {
|
||||
slog.Int("status", status),
|
||||
slog.Int64("duration_ms", time.Since(start).Milliseconds()),
|
||||
}
|
||||
if id := requestID(req); id != "" {
|
||||
attrs = append(attrs, slog.String("request_id", id))
|
||||
}
|
||||
if clientGone(req.Context()) {
|
||||
attrs = append(attrs, slog.Bool("client_gone", true))
|
||||
}
|
||||
@@ -97,14 +111,14 @@ func markPanicked(req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// accessLogger is the app's published logger, else slog.Default.
|
||||
// accessLogger is the app's published logger, else httpLog.
|
||||
func accessLogger(app *backpack.App) *slog.Logger {
|
||||
if app != nil {
|
||||
if log, ok := app.Lookup[*slog.Logger](); ok && log != nil {
|
||||
return log
|
||||
}
|
||||
}
|
||||
return slog.Default()
|
||||
return httpLog
|
||||
}
|
||||
|
||||
// recordPattern stores pattern in the request's access log entry, when the
|
||||
|
||||
Reference in New Issue
Block a user