Move ssl/tls related error to debug loglevel, to avoid log cluttering.

This commit is contained in:
eleksir
2026-04-04 13:16:17 +03:00
parent 14b7a17b00
commit 958ef67f29
5 changed files with 162 additions and 14 deletions

View File

@@ -3,6 +3,7 @@ package backer
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
@@ -14,6 +15,36 @@ import (
"backer/internal/log"
)
// isTLSError checks if the given error is TLS-related.
// It checks for both TLS-specific error types and error messages containing "tls:".
func isTLSError(err error) bool {
if err == nil {
return false
}
var alertErr *tls.AlertError
if errors.As(err, &alertErr) && alertErr != nil {
return true
}
var certErr *tls.CertificateVerificationError
if errors.As(err, &certErr) && certErr != nil {
return true
}
var echErr *tls.ECHRejectionError
if errors.As(err, &echErr) && echErr != nil {
return true
}
var recordErr *tls.RecordHeaderError
if errors.As(err, &recordErr) && recordErr != nil {
return true
}
return strings.HasPrefix(err.Error(), "tls:")
}
// 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) {
@@ -23,7 +54,8 @@ func withServerHeader(next http.Handler) http.Handler {
}
// NewServer starts new server instance with given config file.
func NewServer(configPath string) (*http.Server, error) {
// It returns a serverWrapper that intercepts and classifies server errors.
func NewServer(configPath string) (*serverWrapper, error) { //nolint: revive
_ = log.Init("info", "") // Initialize logger with defaults for early logging.
if err := LoadConfig(configPath); err != nil {
@@ -139,21 +171,27 @@ func NewServer(configPath string) (*http.Server, error) {
// HTTP mode if nohttps is enabled in config.
// HTTPS mode if nohttps is disabled in config.
if C.NoHTTPS {
return &http.Server{
return &serverWrapper{
Server: &http.Server{
Addr: fmt.Sprintf("%s:%d", C.Address, C.Port),
Handler: withServerHeader(mux),
ReadHeaderTimeout: readHeaderTimeout,
WriteTimeout: time.Duration(C.BackupTimeout) * time.Minute,
ErrorLog: log.DebugLogger(),
},
}, nil
}
return &serverWrapper{
Server: &http.Server{
Addr: fmt.Sprintf("%s:%d", C.Address, C.Port),
Handler: withServerHeader(mux),
ReadHeaderTimeout: readHeaderTimeout,
WriteTimeout: time.Duration(C.BackupTimeout) * time.Minute,
}, nil
}
return &http.Server{
Addr: fmt.Sprintf("%s:%d", C.Address, C.Port),
Handler: withServerHeader(mux),
ReadHeaderTimeout: readHeaderTimeout,
WriteTimeout: time.Duration(C.BackupTimeout) * time.Minute,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS13,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS13,
},
ErrorLog: log.DebugLogger(),
},
}, nil
}
@@ -235,4 +273,42 @@ func getCompressionAlgorithm(requestedExt string) string {
}
}
// serverWrapper wraps http.Server to intercept and classify errors.
type (
serverWrapper struct {
*http.Server
}
)
// 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 {
err := sw.ListenAndServe()
if err != nil {
if isTLSError(err) {
log.Debugf("Server error: %v", err)
} else {
log.Warnf("Server error: %v", err)
}
}
return err
}
// ServeTLS starts the TLS server and classifies any errors returned.
func (sw *serverWrapper) ServeTLS(certFile, keyFile string) error {
err := sw.ListenAndServeTLS(certFile, keyFile)
if err != nil {
if isTLSError(err) {
log.Debugf("Server TLS error: %v", err)
} else {
log.Warnf("Server TLS error: %v", err)
}
}
return err
}
/* vim: setlocal ft=go noet ai ts=4 sw=4 sts=4: */

View File

@@ -2,9 +2,12 @@
package log
import (
"context"
"fmt"
"log"
"log/slog"
"os"
"strings"
"time"
)
@@ -13,6 +16,13 @@ var (
Log *os.File
)
// slogWriter implements io.Writer to redirect standard log to slog.
type (
slogWriter struct {
handler slog.Handler
}
)
// Init sets up logger stuff.
// level can be error, warn, info, debug; if something other is supplied, info level is selected.
func Init(level, filename string) error {
@@ -122,6 +132,44 @@ func Debugf(format string, a ...any) {
slog.Debug(fmt.Sprintf(format, a...))
}
// DebugLogger returns a standard library logger that writes to debug level.
// This is useful for assigning to http.Server.ErrorLog to move TLS handshake
// and other client-side errors to debug level.
func DebugLogger() *log.Logger {
opts := &slog.HandlerOptions{
Level: slog.LevelDebug,
}
handler := slog.NewTextHandler(Log, opts)
return log.New(&slogWriter{handler: handler}, "", 0)
}
// Write implements io.Writer. It differentiates TLS/SSL errors from other errors.
// TLS-related errors are logged at debug level, while other errors (handler issues,
// unexpected behavior) are logged at warn level.
func (w *slogWriter) Write(p []byte) (int, error) {
msg := string(p)
isTLSError := strings.Contains(msg, "tls") ||
strings.Contains(msg, "ssl") ||
strings.Contains(msg, "TLS") ||
strings.Contains(msg, "SSL") ||
strings.Contains(msg, "handshake") ||
strings.Contains(msg, "certificate")
level := slog.LevelWarn
if isTLSError {
level = slog.LevelDebug
}
record := slog.NewRecord(time.Now(), level, msg, 0)
_ = w.handler.Handle(context.TODO(), record)
return len(p), nil
}
// Fatal logs args at error level and quits with status code 1.
func Fatal(args ...any) {
slog.Error(fmt.Sprint(args...))