feat(httpserver,log): /healthz, graceful shutdown, slog constructor
Implements internal/httpserver and internal/log. httpserver (forgejo-mcp-broker-8ei): - Server struct owns the HTTP lifecycle; Run(ctx) blocks, Handler() returns the composed handler for unit tests - GET /healthz returns JSON with status, version, git_revision, build_date, and store probe result. Returns 503 when the store reports unhealthy - Signal handling delegated to the caller via ctx cancellation — main wires signal.NotifyContext, httpserver just responds to Done() - Graceful shutdown with a configurable deadline (default 10s). When the deadline expires, falls back to http.Server.Close() so lingering connections are forcibly terminated — http.Server.Shutdown alone never interrupts active connections - ExtraHandler extension point for the OAuth + MCP routes that land in phase 2 and phase 5, so the server doesn't need to be re-plumbed later log: - Small slog wrapper: New(w, debug) returns a JSON logger that stamps every record with service/version/git_rev for correlation across deployments - Discard() helper for tests Tests: 97.9% coverage on httpserver (all health states, wrong-method, ExtraHandler dispatch, ctx-cancel shutdown, shutdown-deadline force-close of hanging requests, missing-field errors), 100% on log. Closes forgejo-mcp-broker-8ei. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
df2253398b
commit
36722940eb
7 changed files with 506 additions and 14 deletions
32
internal/log/log.go
Normal file
32
internal/log/log.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Package log constructs the process-wide structured logger.
|
||||
//
|
||||
// Every record carries the broker's build info so aggregated log backends can
|
||||
// correlate events across versions and deployments.
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
|
||||
"kode.naiv.no/olemd/forgejo-mcp-broker/internal/buildinfo"
|
||||
)
|
||||
|
||||
// New returns a JSON slog.Logger writing to w. When debug is true the
|
||||
// logger starts at Debug level, otherwise at Info.
|
||||
func New(w io.Writer, debug bool) *slog.Logger {
|
||||
level := slog.LevelInfo
|
||||
if debug {
|
||||
level = slog.LevelDebug
|
||||
}
|
||||
h := slog.NewJSONHandler(w, &slog.HandlerOptions{Level: level})
|
||||
return slog.New(h).With(
|
||||
slog.String("service", "fjmcp-broker"),
|
||||
slog.String("version", buildinfo.Version),
|
||||
slog.String("git_rev", buildinfo.GitRevision),
|
||||
)
|
||||
}
|
||||
|
||||
// Discard returns a logger that drops every record. Useful in tests.
|
||||
func Discard() *slog.Logger {
|
||||
return slog.New(slog.DiscardHandler)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue