feat(lib): add log filtering rules

Closes #942

Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
Xe Iaso
2025-08-18 10:51:14 +00:00
parent 53516738c1
commit a7a5e0d5c7
13 changed files with 128 additions and 142 deletions
-45
View File
@@ -1,31 +1,10 @@
package internal
import (
"fmt"
"log"
"log/slog"
"net/http"
"os"
"strings"
)
func InitSlog(level string) {
var programLevel slog.Level
if err := (&programLevel).UnmarshalText([]byte(level)); err != nil {
fmt.Fprintf(os.Stderr, "invalid log level %s: %v, using info\n", level, err)
programLevel = slog.LevelInfo
}
leveler := &slog.LevelVar{}
leveler.Set(programLevel)
h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
AddSource: true,
Level: leveler,
})
slog.SetDefault(slog.New(h))
}
func GetRequestLogger(base *slog.Logger, r *http.Request) *slog.Logger {
host := r.Host
if host == "" {
@@ -44,27 +23,3 @@ func GetRequestLogger(base *slog.Logger, r *http.Request) *slog.Logger {
"x-real-ip", r.Header.Get("X-Real-Ip"),
)
}
// ErrorLogFilter is used to suppress "context canceled" logs from the http server when a request is canceled (e.g., when a client disconnects).
type ErrorLogFilter struct {
Unwrap *log.Logger
}
func (elf *ErrorLogFilter) Write(p []byte) (n int, err error) {
logMessage := string(p)
if strings.Contains(logMessage, "context canceled") {
return len(p), nil // Suppress the log by doing nothing
}
if strings.Contains(logMessage, "Unsolicited response received on idle HTTP channel") {
return len(p), nil
}
if elf.Unwrap != nil {
return elf.Unwrap.Writer().Write(p)
}
return len(p), nil
}
func GetFilteredHTTPLogger() *log.Logger {
stdErrLogger := log.New(os.Stderr, "", log.LstdFlags) // essentially what the default logger is.
return log.New(&ErrorLogFilter{Unwrap: stdErrLogger}, "", 0)
}
-82
View File
@@ -1,82 +0,0 @@
package internal
import (
"bytes"
"log"
"log/slog"
"net/http"
"strings"
"testing"
)
func TestErrorLogFilter(t *testing.T) {
var buf bytes.Buffer
destLogger := log.New(&buf, "", 0)
errorFilterWriter := &ErrorLogFilter{Unwrap: destLogger}
testErrorLogger := log.New(errorFilterWriter, "", 0)
// Test Case 1: Suppressed message
suppressedMessage := "http: proxy error: context canceled"
testErrorLogger.Println(suppressedMessage)
if buf.Len() != 0 {
t.Errorf("Suppressed message was written to output. Output: %q", buf.String())
}
buf.Reset()
// Test Case 2: Allowed message
allowedMessage := "http: another error occurred"
testErrorLogger.Println(allowedMessage)
output := buf.String()
if !strings.Contains(output, allowedMessage) {
t.Errorf("Allowed message was not written to output. Output: %q", output)
}
if !strings.HasSuffix(output, "\n") {
t.Errorf("Allowed message output is missing newline. Output: %q", output)
}
buf.Reset()
// Test Case 3: Partially matching message (should be suppressed)
partiallyMatchingMessage := "Some other log before http: proxy error: context canceled and after"
testErrorLogger.Println(partiallyMatchingMessage)
if buf.Len() != 0 {
t.Errorf("Partially matching message was written to output. Output: %q", buf.String())
}
buf.Reset()
}
func TestGetRequestLogger(t *testing.T) {
// Test case 1: Normal request with Host header
req1, _ := http.NewRequest("GET", "http://example.com/test", nil)
req1.Host = "example.com"
logger := slog.Default()
reqLogger := GetRequestLogger(logger, req1)
// We can't easily test the actual log output without setting up a test handler,
// but we can verify the function doesn't panic and returns a logger
if reqLogger == nil {
t.Error("GetRequestLogger returned nil")
}
// Test case 2: Subrequest auth mode with X-Forwarded-Host
req2, _ := http.NewRequest("GET", "http://test.com/auth", nil)
req2.Host = ""
req2.Header.Set("X-Forwarded-Host", "original-site.com")
reqLogger2 := GetRequestLogger(logger, req2)
if reqLogger2 == nil {
t.Error("GetRequestLogger returned nil for X-Forwarded-Host case")
}
// Test case 3: No host information available
req3, _ := http.NewRequest("GET", "http://test.com/nohost", nil)
req3.Host = ""
reqLogger3 := GetRequestLogger(logger, req3)
if reqLogger3 == nil {
t.Error("GetRequestLogger returned nil for no host case")
}
}