From c219ef6455d8518389dbbbea179719bf8b84f00a Mon Sep 17 00:00:00 2001 From: richjohnrambo Date: Sun, 11 Oct 2026 22:50:54 +0800 Subject: [PATCH] fix(server): suppress expected websocket close write errors --- server/hdl_websock.go | 15 +++++++++++++-- server/hdl_websock_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/server/hdl_websock.go b/server/hdl_websock.go index 3c69ca6..57d1157 100644 --- a/server/hdl_websock.go +++ b/server/hdl_websock.go @@ -214,11 +214,22 @@ func (sess *Session) writeWSFrame(mt int, msg any) error { } reason := classifyWSError(err) immetrics.ObserveWSWrite(frame, "error", reason, started, payloadBytes) - logs.Err.Printf("ws: writeLoop sid=%s uid=%s frame=%s queue=%d capacity=%d reason=%s err=%v", - sess.sid, sess.uid.UserId(), frame, len(sess.send), cap(sess.send), reason, err) + if shouldLogWSWriteError(reason) { + logs.Err.Printf("ws: writeLoop sid=%s uid=%s frame=%s queue=%d capacity=%d reason=%s err=%v", + sess.sid, sess.uid.UserId(), frame, len(sess.send), cap(sess.send), reason, err) + } return err } +func shouldLogWSWriteError(reason string) bool { + switch reason { + case "close_normal", "close_going_away", "closed", "eof", "broken_pipe", "connection_reset": + return false + default: + return true + } +} + func classifyWSError(err error) string { if err == nil { return "none" diff --git a/server/hdl_websock_test.go b/server/hdl_websock_test.go index 5e740e0..6cdc25a 100644 --- a/server/hdl_websock_test.go +++ b/server/hdl_websock_test.go @@ -37,6 +37,31 @@ func TestClassifyWSError(t *testing.T) { } } +func TestShouldLogWSWriteError(t *testing.T) { + tests := []struct { + reason string + want bool + }{ + {reason: "close_normal", want: false}, + {reason: "close_going_away", want: false}, + {reason: "closed", want: false}, + {reason: "eof", want: false}, + {reason: "broken_pipe", want: false}, + {reason: "connection_reset", want: false}, + {reason: "timeout", want: true}, + {reason: "close_abnormal", want: true}, + {reason: "other", want: true}, + } + + for _, tc := range tests { + t.Run(tc.reason, func(t *testing.T) { + if got := shouldLogWSWriteError(tc.reason); got != tc.want { + t.Fatalf("shouldLogWSWriteError(%q)=%v want %v", tc.reason, got, tc.want) + } + }) + } +} + func TestWebSocketOutboundQueueTiming(t *testing.T) { sess := &Session{proto: WEBSOCK, send: make(chan any, 2)} payload := []byte("message")