From 1f7fcf938b920b4ff3d4c8c72c87f0a9022cbf26 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Thu, 31 Jul 2025 08:06:35 -0400 Subject: [PATCH] fix(lib): add the ability to set a custom slog Logger (#915) * fix(lib): add the ability to set a custom slog Logger Closes #864 * test(lib): amend s.check usage Signed-off-by: Xe Iaso --------- Signed-off-by: Xe Iaso Signed-off-by: Xe Iaso --- docs/docs/CHANGELOG.md | 1 + internal/log.go | 4 ++-- lib/anubis.go | 21 +++++++++++---------- lib/anubis_test.go | 6 +++--- lib/config.go | 8 +++++++- lib/http.go | 2 +- 6 files changed, 25 insertions(+), 17 deletions(-) diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 0d1067b..e9e8da4 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +- Downstream consumers can change the default [log/slog#Logger](https://pkg.go.dev/log/slog#Logger) instance that Anubis uses by setting `opts.Logger` to your slog instance of choice ([#864](https://github.com/TecharoHQ/anubis/issues/864)). - The [Thoth client](https://anubis.techaro.lol/docs/admin/thoth) is now public in the repo instead of being an internal package. - [Custom-AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client)'s default User-Agent has an increased weight by default ([#852](https://github.com/TecharoHQ/anubis/issues/852)). - The [`segments`](./admin/configuration/expressions.mdx#segments) function was added for splitting a path into its slash-separated segments. diff --git a/internal/log.go b/internal/log.go index c3a6a0f..1f9e0e7 100644 --- a/internal/log.go +++ b/internal/log.go @@ -26,8 +26,8 @@ func InitSlog(level string) { slog.SetDefault(slog.New(h)) } -func GetRequestLogger(r *http.Request) *slog.Logger { - return slog.With( +func GetRequestLogger(base *slog.Logger, r *http.Request) *slog.Logger { + return base.With( "host", r.Host, "method", r.Method, "path", r.URL.Path, diff --git a/lib/anubis.go b/lib/anubis.go index 06b030a..790985f 100644 --- a/lib/anubis.go +++ b/lib/anubis.go @@ -75,6 +75,7 @@ type Server struct { hs512Secret []byte opts Options store store.Interface + logger *slog.Logger } func (s *Server) getTokenKeyfunc() jwt.Keyfunc { @@ -150,7 +151,7 @@ func (s *Server) maybeReverseProxyOrPage(w http.ResponseWriter, r *http.Request) } func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpStatusOnly bool) { - lg := internal.GetRequestLogger(r) + lg := internal.GetRequestLogger(s.logger, r) // Adjust cookie path if base prefix is not empty cookiePath := "/" @@ -158,7 +159,7 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS cookiePath = strings.TrimSuffix(anubis.BasePrefix, "/") + "/" } - cr, rule, err := s.check(r) + cr, rule, err := s.check(r, lg) if err != nil { lg.Error("check failed", "err", err) localizer := localization.GetLocalizer(r) @@ -274,7 +275,7 @@ func (s *Server) checkRules(w http.ResponseWriter, r *http.Request, cr policy.Ch return true default: s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host}) - slog.Error("CONFIG ERROR: unknown rule", "rule", cr.Rule) + lg.Error("CONFIG ERROR: unknown rule", "rule", cr.Rule) s.respondWithError(w, r, fmt.Sprintf("%s \"maybeReverseProxy.Rules\"", localizer.T("internal_server_error"))) return true } @@ -310,7 +311,7 @@ func (s *Server) handleDNSBL(w http.ResponseWriter, r *http.Request, ip string, } func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) { - lg := internal.GetRequestLogger(r) + lg := internal.GetRequestLogger(s.logger, r) localizer := localization.GetLocalizer(r) redir := r.FormValue("redir") @@ -329,7 +330,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) { r.URL.Path = redir encoder := json.NewEncoder(w) - cr, rule, err := s.check(r) + cr, rule, err := s.check(r, lg) if err != nil { lg.Error("check failed", "err", err) w.WriteHeader(http.StatusInternalServerError) @@ -381,7 +382,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) { } func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) { - lg := internal.GetRequestLogger(r) + lg := internal.GetRequestLogger(s.logger, r) localizer := localization.GetLocalizer(r) redir := r.FormValue("redir") @@ -428,7 +429,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) { return } - cr, rule, err := s.check(r) + cr, rule, err := s.check(r, lg) if err != nil { lg.Error("check failed", "err", err) s.respondWithError(w, r, fmt.Sprintf("%s \"passChallenge\"", localizer.T("internal_server_error"))) @@ -509,7 +510,7 @@ func cr(name string, rule config.Rule, weight int) policy.CheckResult { } // Check evaluates the list of rules, and returns the result -func (s *Server) check(r *http.Request) (policy.CheckResult, *policy.Bot, error) { +func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.CheckResult, *policy.Bot, error) { host := r.Header.Get("X-Real-Ip") if host == "" { return decaymap.Zilch[policy.CheckResult](), nil, fmt.Errorf("[misconfiguration] X-Real-Ip header is not set") @@ -533,7 +534,7 @@ func (s *Server) check(r *http.Request) (policy.CheckResult, *policy.Bot, error) case config.RuleDeny, config.RuleAllow, config.RuleBenchmark, config.RuleChallenge: return cr("bot/"+b.Name, b.Action, weight), &b, nil case config.RuleWeigh: - slog.Debug("adjusting weight", "name", b.Name, "delta", b.Weight.Adjust) + lg.Debug("adjusting weight", "name", b.Name, "delta", b.Weight.Adjust) weight += b.Weight.Adjust } } @@ -542,7 +543,7 @@ func (s *Server) check(r *http.Request) (policy.CheckResult, *policy.Bot, error) for _, t := range s.policy.Thresholds { result, _, err := t.Program.ContextEval(r.Context(), &policy.ThresholdRequest{Weight: weight}) if err != nil { - slog.Error("error when evaluating threshold expression", "expression", t.Expression.String(), "err", err) + lg.Error("error when evaluating threshold expression", "expression", t.Expression.String(), "err", err) continue } diff --git a/lib/anubis_test.go b/lib/anubis_test.go index c9dedba..e9c9eff 100644 --- a/lib/anubis_test.go +++ b/lib/anubis_test.go @@ -343,7 +343,7 @@ func TestCheckDefaultDifficultyMatchesPolicy(t *testing.T) { req.Header.Add("X-Real-Ip", "127.0.0.1") - cr, bot, err := s.check(req) + cr, bot, err := s.check(req, s.logger) if err != nil { t.Fatal(err) } @@ -583,7 +583,7 @@ func TestCloudflareWorkersRule(t *testing.T) { req.Header.Add("X-Real-Ip", "127.0.0.1") req.Header.Add("Cf-Worker", "true") - cr, _, err := s.check(req) + cr, _, err := s.check(req, s.logger) if err != nil { t.Fatal(err) } @@ -601,7 +601,7 @@ func TestCloudflareWorkersRule(t *testing.T) { req.Header.Add("X-Real-Ip", "127.0.0.1") - cr, _, err := s.check(req) + cr, _, err := s.check(req, s.logger) if err != nil { t.Fatal(err) } diff --git a/lib/config.go b/lib/config.go index 9c6708f..1bfee58 100644 --- a/lib/config.go +++ b/lib/config.go @@ -43,6 +43,7 @@ type Options struct { OpenGraph config.OpenGraph ServeRobotsTXT bool CookieSecure bool + Logger *slog.Logger } func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) { @@ -89,8 +90,12 @@ func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty } func New(opts Options) (*Server, error) { + if opts.Logger == nil { + opts.Logger = slog.With("subsystem", "anubis") + } + if opts.ED25519PrivateKey == nil && opts.HS512Secret == nil { - slog.Debug("opts.PrivateKey not set, generating a new one") + opts.Logger.Debug("opts.PrivateKey not set, generating a new one") _, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { return nil, fmt.Errorf("lib: can't generate private key: %v", err) @@ -108,6 +113,7 @@ func New(opts Options) (*Server, error) { opts: opts, OGTags: ogtags.NewOGTagCache(opts.Target, opts.Policy.OpenGraph, opts.Policy.Store), store: opts.Policy.Store, + logger: opts.Logger, } mux := http.NewServeMux() diff --git a/lib/http.go b/lib/http.go index ad1a244..4f8ed02 100644 --- a/lib/http.go +++ b/lib/http.go @@ -120,7 +120,7 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C return } - lg := internal.GetRequestLogger(r) + lg := internal.GetRequestLogger(s.logger, r) if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") && randomChance(64) { lg.Error("client was given a challenge but does not in fact support gzip compression")