~bigbes/sr-ht-compare

07736d95cf181f89fcd8b0dbb54afe79dfa50352 — bigbes 9 days ago 5719e51
logging: the instance's log policy instead of forty local lines

initLogging held compare's own copy of the level choice, the colour probe
and the mask list, and the mask list was the drifted one: three keys and
a four-word pattern, against an instance whose services between them
redact ten. logging.Defaults answers all of it, and the handler stays
here — scribe is auxilia's and does not belong in the library every
service links for its chrome.

isTerminal goes to logging.ColorEnabled, which also honours NO_COLOR, and
the level now reads $LOG_LEVEL after -d. Defaults(nil, "") because the
handler is installed before LoadConfig: a daemon that became verbose only
after startup is silent for the window -d was passed to watch.
1 files changed, 31 insertions(+), 32 deletions(-)

M cmd/comparesrht/main.go
M cmd/comparesrht/main.go => cmd/comparesrht/main.go +31 -32
@@ 28,7 28,6 @@ import (
	"fmt"
	"log/slog"
	"os"
	"slices"
	"strings"
	"time"



@@ 38,6 37,7 @@ import (
	"go.bigb.es/auxilia/scribe"
	"sourcecraft.dev/bigbes/sr-ht-core/config"
	coreserver "sourcecraft.dev/bigbes/sr-ht-core/server"
	"sourcecraft.dev/bigbes/sr-ht-ecore/logging"
	"sourcecraft.dev/bigbes/sr-ht-ecore/login"

	"sourcecraft.dev/bigbes/sr-ht-compare/authz"


@@ 57,47 57,46 @@ const (
//
// Setting the *default* is the load-bearing part, not the formatting: the
// packages that log below this one hold no logger of their own — the web tier
// calls slog's package functions, and so does ecore's panic-recovery
// middleware, whose report (method, path, panic, stack) would otherwise go to
// Go's plain stderr handler with the stack as one unreadable field.
// calls slog's package functions, and so do ecore's panic-recovery middleware
// and its request logger, whose records would otherwise go to Go's plain
// stderr handler with the stack as one unreadable field.
//
// -d is read straight out of the argument vector because core-go's server.New
// owns the real flag parse and does not run until after this: a daemon that
// only became verbose once it had finished starting would be silent for exactly
// the part of its life an operator passes -d to watch.
func initLogging(debug bool) {
	level := slog.LevelInfo
	if debug {
		level = slog.LevelDebug
	}
	slog.SetDefault(slog.New(scribe.NewTintHandler(
// The policy is ecore's and the handler is ours, which is the split logging
// documents: what is masked and how verbose to be are facts about the instance,
// while a tinting handler is auxilia's and does not belong in a library every
// service links for its page chrome.
//
// Defaults(nil, "") because config.ini is not loaded yet, and deliberately so:
// -d is read straight out of the argument vector — core-go's server.New owns
// the real flag parse and does not run until after config validation, and a
// daemon that only became verbose once it had finished starting would be silent
// for exactly the part of its life an operator passes -d to watch. The two
// knobs that work here are -d and $LOG_LEVEL; a [compare.sr.ht] log-level key
// would be read too late to matter and is not supported.
func initLogging() {
	opts := logging.Defaults(nil, "")
	logging.Install(scribe.NewTintHandler(
		scribe.WithWriter(os.Stderr),
		scribe.WithLevel(level),
		scribe.WithSource(true),
		scribe.WithTimeFormat(time.DateTime),
		scribe.WithLevel(opts.Level),
		scribe.WithSource(opts.AddSource),
		scribe.WithTimeFormat(opts.TimeFormat),
		// Colour is for a terminal; under systemd stderr is the journal, where
		// the escapes are noise in every stored record. Asked of the stdlib
		// rather than of golang.org/x/term, which would be a whole new
		// dependency for one predicate.
		scribe.WithNoColor(!isTerminal(os.Stderr)),
		// the escapes are noise in every stored record. logging.ColorEnabled
		// answers that from one Stat, and honours NO_COLOR besides.
		scribe.WithNoColor(!opts.Color),
		// This daemon logs no credential deliberately, which is precisely why
		// the masks are here: the one that leaks is the attribute somebody adds
		// later, and a request or a cookie is the likeliest thing to be handed
		// to a log line while debugging the very cookie path this service reads.
		scribe.WithMaskKeys("token", "cookie", "authorization"),
		scribe.WithMask(`(?i)(secret|token|api_?key|password)`, "***"),
	)))
}

// isTerminal reports whether f is a character device — a tty rather than the
// pipe systemd, a build runner or a shell redirect hands the process.
func isTerminal(f *os.File) bool {
	info, err := f.Stat()
	return err == nil && info.Mode()&os.ModeCharDevice != 0
		// The list is the instance's, so a key another service learned to redact
		// is redacted here without anybody editing this file.
		scribe.WithMaskKeys(opts.MaskKeys...),
		scribe.WithMask(opts.MaskPattern, opts.MaskReplacement),
	))
}

func main() {
	initLogging(slices.Contains(os.Args[1:], "-d"))
	initLogging()

	// LoadConfig never panics on a missing file (it returns a nil ini.File);
	// validateConfig turns any absent required key into a single clear fatal.