diff --git a/server/server.go b/server/server.go index 787ad05..b4305f0 100644 --- a/server/server.go +++ b/server/server.go @@ -1,11 +1,13 @@ package server import ( + "bytes" "context" "crypto" "crypto/tls" "crypto/x509" "fmt" + "io" "log" "net" "net/http" @@ -40,7 +42,24 @@ type ListenOpts struct { // Override legacy behavior where server logs written to the application's logrus object // were dropped unless logrus was set to debug-level (such as by launching steve with '--debug'). // Setting this to true results in server logs appearing at an ERROR level. - DisplayServerLogs bool + DisplayServerLogs bool + IgnoreTLSHandshakeError bool +} + +var TLSHandshakeError = []byte("http: TLS handshake error") + +var _ io.Writer = &TLSErrorDebugger{} + +type TLSErrorDebugger struct{} + +func (t *TLSErrorDebugger) Write(p []byte) (n int, err error) { + p = bytes.TrimSpace(p) + if bytes.HasPrefix(p, TLSHandshakeError) { + logrus.Debug(string(p)) + } else { + logrus.Error(string(p)) + } + return len(p), err } func ListenAndServe(ctx context.Context, httpsPort, httpPort int, handler http.Handler, opts *ListenOpts) error { @@ -52,9 +71,15 @@ func ListenAndServe(ctx context.Context, httpsPort, httpPort int, handler http.H if opts.DisplayServerLogs { writer = logger.WriterLevel(logrus.ErrorLevel) } - // Otherwise preserve legacy behaviour of displaying server logs only in debug mode. - errorLog := log.New(writer, "", log.LstdFlags) + var errorLog *log.Logger + if opts.IgnoreTLSHandshakeError { + debugWriter := &TLSErrorDebugger{} + errorLog = log.New(debugWriter, "", 0) + } else { + // Otherwise preserve legacy behaviour of displaying server logs only in debug mode. + errorLog = log.New(writer, "", 0) + } if opts.TLSListenerConfig.TLSConfig == nil { opts.TLSListenerConfig.TLSConfig = &tls.Config{} diff --git a/server/server_test.go b/server/server_test.go index 28a17b1..010c09b 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -39,6 +39,47 @@ func (s *safeWriter) Write(p []byte) (n int, err error) { return s.writer.Write(p) } +func TestTLSHandshakeErrorWriter(t *testing.T) { + tests := []struct { + name string + ignoreTLSHandshakeError bool + message []byte + expectedLevel logrus.Level + }{ + { + name: "TLS handshake error is logged as debug", + message: []byte("http: TLS handshake error: EOF"), + expectedLevel: logrus.DebugLevel, + }, + { + name: "other errors are logged as error", + message: []byte("some other server error"), + expectedLevel: logrus.ErrorLevel, + }, + } + var baseLogLevel = logrus.GetLevel() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assertPkg.New(t) + + var buf bytes.Buffer + logrus.SetOutput(&buf) + logrus.SetLevel(logrus.DebugLevel) + + debugger := &TLSErrorDebugger{} + n, err := debugger.Write(tt.message) + + assert.Nil(err) + assert.Equal(len(tt.message), n) + + logOutput := buf.String() + assert.Contains(logOutput, "level="+tt.expectedLevel.String()) + assert.Contains(logOutput, string(tt.message)) + }) + } + logrus.SetLevel(baseLogLevel) +} + func TestHttpServerLogWithLogrus(t *testing.T) { assert := assertPkg.New(t) message := "debug-level writer" @@ -84,7 +125,7 @@ func doRequest(safeWriter *safeWriter, message string, logLevel logrus.Level) er msg := fmt.Sprintf("panicking context: %s", message) handler := alwaysPanicHandler{msg: msg} listenOpts := &ListenOpts{ - BindHost: host, + BindHost: host, DisplayServerLogs: logLevel == logrus.ErrorLevel, }