@@ 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.