Compare commits
3 Commits
b87d520b61
...
51897b20bd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51897b20bd | ||
|
|
8701938c75 | ||
|
|
7df8bc1446 |
13
AGENTS.md
13
AGENTS.md
@@ -276,13 +276,16 @@ Checks `X-Forwarded-For` header first (for proxied requests), then falls back to
|
|||||||
|
|
||||||
### TLS Error Handling
|
### TLS Error Handling
|
||||||
|
|
||||||
The server uses a `ServerWrapper` type to intercept and classify errors from `Serve()` and `ServeTLS()`. TLS-specific errors are logged at debug level to reduce log clutter:
|
The server uses a `ServerWrapper` type to intercept and classify errors from `Serve()` and `ServeTLS()`. Two mechanisms:
|
||||||
|
|
||||||
- Uses `errors.As` to detect TLS error types: `tls.AlertError`, `tls.CertificateVerificationError`, `tls.ECHRejectionError`, `tls.RecordHeaderError`
|
1. **Server errors** (`Serve()` / `ServeTLS()`): Uses `errors.As` to detect TLS error types (`tls.AlertError`, `tls.CertificateVerificationError`, `tls.ECHRejectionError`, `tls.RecordHeaderError`) and checks for "tls:" prefix. TLS-specific errors logged at debug level, others at warn level.
|
||||||
- Also checks for "tls:" prefix in error messages
|
|
||||||
- Additionally uses `http.Server.ErrorLog` with a custom logger that writes at debug level for net/http's internal TLS-related messages
|
|
||||||
|
|
||||||
This ensures client-side TLS errors (handshake failures, certificate issues) don't clutter production logs.
|
2. **net/http internal errors** (`http.Server.ErrorLog`): Uses `DebugLogger()` which routes messages through `slogWriter.Write()`:
|
||||||
|
- TLS-related errors (keywords: "tls", "ssl", "handshake", "certificate") → logged at debug level
|
||||||
|
- Other errors → logged at warn level
|
||||||
|
- Both are filtered by configured `loglevel`. Set `loglevel: debug` to see all messages.
|
||||||
|
|
||||||
|
This ensures client-side TLS errors (handshake failures, certificate issues) don't clutter production logs by default.
|
||||||
|
|
||||||
### Compression Level Mapping
|
### Compression Level Mapping
|
||||||
|
|
||||||
|
|||||||
@@ -399,7 +399,7 @@ type (
|
|||||||
*http.Server
|
*http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
// responseWriterWrapper wraps http.ResponseWriter to capture status code.
|
// responseWriterWrapper wraps [http.ResponseWriter] to capture status code.
|
||||||
responseWriterWrapper struct {
|
responseWriterWrapper struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
statusCode int
|
statusCode int
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@@ -27,11 +26,9 @@ var (
|
|||||||
logLevel slog.Level
|
logLevel slog.Level
|
||||||
)
|
)
|
||||||
|
|
||||||
// slogWriter implements [io.Writer] to redirect standard log to slog.
|
|
||||||
type (
|
type (
|
||||||
slogWriter struct {
|
// slogWriter implements [io.Writer] to redirect standard log to slog.
|
||||||
handler slog.Handler
|
slogWriter struct{}
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Init sets up logger stuff.
|
// Init sets up logger stuff.
|
||||||
@@ -154,9 +151,9 @@ func Debugf(format string, a ...any) {
|
|||||||
slog.Debug(fmt.Sprintf(format, a...))
|
slog.Debug(fmt.Sprintf(format, a...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebugLogger returns a standard library logger that writes at the configured log level.
|
// DebugLogger returns a standard library logger that writes at debug/warn level.
|
||||||
// This is useful for assigning to http.Server.ErrorLog to route net/http errors
|
// TLS-related errors are logged at debug level, others at warn level.
|
||||||
// through our logger while respecting the configured log level.
|
// Both are filtered by the configured log level: set loglevel to debug to see all messages.
|
||||||
func DebugLogger() *log.Logger {
|
func DebugLogger() *log.Logger {
|
||||||
mu.RLock()
|
mu.RLock()
|
||||||
defer mu.RUnlock()
|
defer mu.RUnlock()
|
||||||
@@ -167,7 +164,9 @@ func DebugLogger() *log.Logger {
|
|||||||
|
|
||||||
handler := slog.NewTextHandler(Log, opts)
|
handler := slog.NewTextHandler(Log, opts)
|
||||||
|
|
||||||
return log.New(&slogWriter{handler: handler}, "", 0)
|
_ = handler // satisfy linter (handler configures level for potential future use).
|
||||||
|
|
||||||
|
return log.New(&slogWriter{}, "", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write implements [io.Writer]. It differentiates TLS/SSL errors from other errors.
|
// Write implements [io.Writer]. It differentiates TLS/SSL errors from other errors.
|
||||||
@@ -183,15 +182,12 @@ func (w *slogWriter) Write(p []byte) (int, error) {
|
|||||||
strings.Contains(msg, "handshake") ||
|
strings.Contains(msg, "handshake") ||
|
||||||
strings.Contains(msg, "certificate")
|
strings.Contains(msg, "certificate")
|
||||||
|
|
||||||
level := slog.LevelWarn
|
|
||||||
if isTLSError {
|
if isTLSError {
|
||||||
level = slog.LevelDebug
|
Debug(msg)
|
||||||
|
} else {
|
||||||
|
Warn(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
record := slog.NewRecord(time.Now(), level, msg, 0)
|
|
||||||
|
|
||||||
_ = w.handler.Handle(context.TODO(), record)
|
|
||||||
|
|
||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ func TestDebugLogger(t *testing.T) {
|
|||||||
logFilePath := tmpFile.Name()
|
logFilePath := tmpFile.Name()
|
||||||
tmpFile.Close()
|
tmpFile.Close()
|
||||||
|
|
||||||
if err := Init("info", logFilePath); err != nil {
|
if err := Init("debug", logFilePath); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer Close()
|
defer Close()
|
||||||
@@ -385,4 +385,85 @@ func TestDebugLogger(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestDebugLoggerTLS tests that TLS errors are logged at debug level.
|
||||||
|
func TestDebugLoggerTLS(t *testing.T) {
|
||||||
|
tmpFile, err := os.CreateTemp(t.TempDir(), "test_log_*.log")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
logFilePath := tmpFile.Name()
|
||||||
|
tmpFile.Close()
|
||||||
|
|
||||||
|
if err := Init("debug", logFilePath); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer Close()
|
||||||
|
|
||||||
|
logger := DebugLogger()
|
||||||
|
logger.Print("tls handshake timeout")
|
||||||
|
|
||||||
|
content, err := os.ReadFile(logFilePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(string(content), "tls handshake timeout") {
|
||||||
|
t.Error("Expected TLS message in log")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDebugLoggerNonTLS tests that non-TLS errors are logged at warn level.
|
||||||
|
func TestDebugLoggerNonTLS(t *testing.T) {
|
||||||
|
tmpFile, err := os.CreateTemp(t.TempDir(), "test_log_*.log")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
logFilePath := tmpFile.Name()
|
||||||
|
tmpFile.Close()
|
||||||
|
|
||||||
|
if err := Init("warn", logFilePath); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer Close()
|
||||||
|
|
||||||
|
logger := DebugLogger()
|
||||||
|
logger.Print("http: connection reset by peer")
|
||||||
|
|
||||||
|
content, err := os.ReadFile(logFilePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(string(content), "http: connection reset by peer") {
|
||||||
|
t.Error("Expected non-TLS message in log")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDebugLoggerLevelFiltering tests that log level filtering works.
|
||||||
|
func TestDebugLoggerLevelFiltering(t *testing.T) {
|
||||||
|
tmpFile, err := os.CreateTemp(t.TempDir(), "test_log_*.log")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
logFilePath := tmpFile.Name()
|
||||||
|
tmpFile.Close()
|
||||||
|
|
||||||
|
if err := Init("info", logFilePath); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer Close()
|
||||||
|
|
||||||
|
logger := DebugLogger()
|
||||||
|
logger.Print("tls handshake timeout") // TLS error at debug level - should be filtered
|
||||||
|
|
||||||
|
content, err := os.ReadFile(logFilePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(string(content), "tls handshake timeout") {
|
||||||
|
t.Error("Debug-level TLS message should be filtered at info log level")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* vim: setlocal ft=go noet ai ts=4 sw=4 sts=4: */
|
/* vim: setlocal ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
|
|||||||
Reference in New Issue
Block a user