Catch all other missing request patterns to logs

This commit is contained in:
eleksir
2026-04-05 09:26:44 +03:00
parent b35851567c
commit 0581d7430c
2 changed files with 133 additions and 2 deletions

View File

@@ -48,6 +48,23 @@ func isTLSError(err error) bool {
return strings.HasPrefix(err.Error(), "tls:")
}
// withStatusLogging wraps a handler to log 404 and 405 responses.
func withStatusLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rw := &responseWriterWrapper{ResponseWriter: w, statusCode: http.StatusOK}
next.ServeHTTP(rw, r)
clientIP := getClientIP(r)
switch rw.statusCode {
case http.StatusNotFound:
log.Infof("Not Found from %s to %s", clientIP, r.RequestURI)
case http.StatusMethodNotAllowed:
log.Infof("Method Not Allowed from %s for %s %s", clientIP, r.Method, r.RequestURI)
}
})
}
// withServerHeader wraps a handler to add Server header to all responses.
func withServerHeader(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -74,6 +91,13 @@ func NewServer(configPath string) (*ServerWrapper, error) {
mux := http.NewServeMux()
backupHandler := func(w http.ResponseWriter, r *http.Request) {
// Only GET method is allowed.
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
username, password, ok := r.BasicAuth()
// No auth? Good bye.
@@ -219,7 +243,7 @@ func NewServer(configPath string) (*ServerWrapper, error) {
srv := &ServerWrapper{
Server: &http.Server{
Addr: fmt.Sprintf("%s:%d", C.Address, C.Port),
Handler: withServerHeader(mux),
Handler: withServerHeader(withStatusLogging(mux)),
ReadHeaderTimeout: readHeaderTimeout,
WriteTimeout: time.Duration(C.BackupTimeout) * time.Minute,
IdleTimeout: idleTimeout,
@@ -235,7 +259,7 @@ func NewServer(configPath string) (*ServerWrapper, error) {
srv := &ServerWrapper{
Server: &http.Server{
Addr: fmt.Sprintf("%s:%d", C.Address, C.Port),
Handler: withServerHeader(mux),
Handler: withServerHeader(withStatusLogging(mux)),
ReadHeaderTimeout: readHeaderTimeout,
WriteTimeout: time.Duration(C.BackupTimeout) * time.Minute,
IdleTimeout: idleTimeout,
@@ -374,8 +398,20 @@ type (
ServerWrapper struct {
*http.Server
}
// responseWriterWrapper wraps http.ResponseWriter to capture status code.
responseWriterWrapper struct {
http.ResponseWriter
statusCode int
}
)
// WriteHeader captures the status code for logging.
func (rw *responseWriterWrapper) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
// Serve starts the server and classifies any errors returned.
// TLS-related errors are logged at debug level, other errors at warn level.
func (sw *ServerWrapper) Serve() error {

View File

@@ -523,4 +523,99 @@ func TestCopyWithContextCancellation(t *testing.T) {
}
}
// TestStatusLogging404 tests that 404 responses are logged.
func TestStatusLogging404(t *testing.T) {
C = Config{} // Reset global config.
excludePatterns = nil
// Create a minimal mux that returns 404 for all paths
testMux := http.NewServeMux()
testMux.HandleFunc("/archive", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
wrappedMux := withStatusLogging(testMux)
// Use a ResponseRecorder to capture the response
rr := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/nonexistent", nil)
req.RemoteAddr = "127.0.0.1:12345"
wrappedMux.ServeHTTP(rr, req)
if rr.Code != http.StatusNotFound {
t.Errorf("Expected status 404, got %d", rr.Code)
}
}
// TestStatusLogging405 tests that 405 responses are logged.
func TestStatusLogging405(t *testing.T) {
C = Config{} // Reset global config.
excludePatterns = nil
// Create a minimal mux that returns 405 for wrong method
testMux := http.NewServeMux()
testMux.HandleFunc("/archive", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed)
})
wrappedMux := withStatusLogging(testMux)
rr := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/archive", nil)
req.RemoteAddr = "127.0.0.1:12345"
wrappedMux.ServeHTTP(rr, req)
if rr.Code != http.StatusMethodNotAllowed {
t.Errorf("Expected status 405, got %d", rr.Code)
}
}
// TestMethodNotAllowedHandler tests that non-GET methods return 405.
func TestMethodNotAllowedHandler(t *testing.T) {
C = Config{
Address: "0.0.0.0",
Port: 8086,
Location: "/archive",
User: "testuser",
Password: "testpass",
Directories: []string{"../../test_data/test1/foo"},
}
excludePatterns = nil
// Create test server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok || username != C.User || password != C.Password {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if r.Method != http.MethodGet {
w.Header().Set("Server", "backer")
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
// Test POST method
req, _ := http.NewRequest("POST", server.URL+"/archive", nil)
req.SetBasicAuth("testuser", "testpass")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("Request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusMethodNotAllowed {
t.Errorf("Expected status 405, got %d", resp.StatusCode)
}
}
/* vim: setlocal ft=go noet ai ts=4 sw=4 sts=4: */