diff --git a/repository/logging/config.go b/repository/logging/config.go index efc16fcd9..8b27cca75 100644 --- a/repository/logging/config.go +++ b/repository/logging/config.go @@ -1,9 +1,16 @@ package logging +import "strings" + type Config struct { EnableTracing *bool EnableAudit *bool IncludeSQL *bool + // AuditExcludeURIPrefixes skips [AUDIT] logging when the request URI has any + // of these prefixes. Empty/nil means no path-based exclusions (library default: + // audit all URIs when audit is enabled). Clients configure this, e.g. + // ["/v1/api/meta/"] for metric/status scrape noise. + AuditExcludeURIPrefixes []string } func (c *Config) IsTracingEnabled() bool { @@ -26,3 +33,23 @@ func (c *Config) ShallIncludeSQL() bool { } return *c.IncludeSQL } + +// ShouldAuditURI reports whether [AUDIT] should be emitted for the request URI. +func (c *Config) ShouldAuditURI(uri string) bool { + if !c.IsAuditEnabled() { + return false + } + if c == nil || len(c.AuditExcludeURIPrefixes) == 0 { + return true + } + path := uri + if i := strings.IndexByte(uri, '?'); i >= 0 { + path = uri[:i] + } + for _, prefix := range c.AuditExcludeURIPrefixes { + if prefix != "" && strings.HasPrefix(path, prefix) { + return false + } + } + return true +} diff --git a/repository/logging/logging.go b/repository/logging/logging.go index e32077141..9a4938943 100644 --- a/repository/logging/logging.go +++ b/repository/logging/logging.go @@ -18,7 +18,7 @@ func Log(config *Config, execContext *exec.Context) { if !includeSQL { snap.Metrics = snap.Metrics.HideMetrics() } - if config.IsAuditEnabled() { + if config.ShouldAuditURI(execContext.URI) { data := safeMarshal("EXECCONTEXT", snap) fmt.Println("[AUDIT]", string(data)) } diff --git a/repository/logging/logging_test.go b/repository/logging/logging_test.go index 3214c22e0..9681b913e 100644 --- a/repository/logging/logging_test.go +++ b/repository/logging/logging_test.go @@ -256,3 +256,89 @@ func TestLog_RedactsAuditAndTraceErrorsBeforeEmission(t *testing.T) { assert.Contains(t, logged, "[TRACE]") assert.Contains(t, logged, redactedValue) } + +func TestShouldAuditURI(t *testing.T) { + enabled := true + disabled := false + + cases := []struct { + name string + config *Config + uri string + want bool + }{ + { + name: "nil config audits all when enabled by zero value", + config: &Config{}, + uri: "/v1/api/meta/metric/mdp/adorder/operation/foo/recent", + want: true, + }, + { + name: "no excludes keeps business routes", + config: &Config{}, + uri: "/v1/api/mdp/kpiperf/produce", + want: true, + }, + { + name: "configured exclude skips matching prefix", + config: &Config{AuditExcludeURIPrefixes: []string{"/v1/api/meta/"}}, + uri: "/v1/api/meta/metric/x", + want: false, + }, + { + name: "strips query before prefix match", + config: &Config{AuditExcludeURIPrefixes: []string{"/v1/api/meta/"}}, + uri: "/v1/api/meta/metric/x?foo=1", + want: false, + }, + { + name: "non-matching URI still audited", + config: &Config{AuditExcludeURIPrefixes: []string{"/v1/api/meta/"}}, + uri: "/v1/api/mdp/kpiperf/produce", + want: true, + }, + { + name: "EnableAudit false disables all", + config: &Config{EnableAudit: &disabled}, + uri: "/v1/api/mdp/kpiperf/produce", + want: false, + }, + { + name: "empty exclude list audits meta", + config: &Config{EnableAudit: &enabled, AuditExcludeURIPrefixes: []string{}}, + uri: "/v1/api/meta/metric/x", + want: true, + }, + { + name: "custom exclude prefix", + config: &Config{AuditExcludeURIPrefixes: []string{"/v1/api/mdp/internal/"}}, + uri: "/v1/api/mdp/internal/health", + want: false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.want, tc.config.ShouldAuditURI(tc.uri)) + }) + } +} + +func TestLog_SkipsExcludedAudit(t *testing.T) { + oldStdout := os.Stdout + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stdout = w + + execCtx := exec.NewContext("GET", "/v1/api/meta/metric/mdp/adorder/operation/foo/recent", nil, "") + Log(&Config{AuditExcludeURIPrefixes: []string{"/v1/api/meta/"}}, execCtx) + + require.NoError(t, w.Close()) + os.Stdout = oldStdout + defer r.Close() + + var output bytes.Buffer + _, err = io.Copy(&output, r) + require.NoError(t, err) + assert.NotContains(t, output.String(), "[AUDIT]") +}