From f65fac147dbb36e620cf23374cae50fcc7482f62 Mon Sep 17 00:00:00 2001 From: Edward Houston Date: Tue, 16 Jun 2026 08:33:32 +0200 Subject: [PATCH] fix(electrum): stop logging full response payload on client disconnect Broken-pipe/reset errors during send embedded the entire JSON-RPC reply (e.g. a large get_history result) into the error context, which was then logged at ERROR and split across many Cloud Logging entries. Truncate the send-failure context to a byte count and route client disconnects to debug!. --- src/electrum/server.rs | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/electrum/server.rs b/src/electrum/server.rs index 873a8dc20..5bedb3a3c 100644 --- a/src/electrum/server.rs +++ b/src/electrum/server.rs @@ -553,7 +553,7 @@ impl Connection { let line = value.to_string() + "\n"; self.stream .write_all(line.as_bytes()) - .chain_err(|| format!("failed to send {}", value))?; + .chain_err(|| format!("failed to send response ({} bytes)", line.len()))?; } Ok(()) } @@ -676,11 +676,16 @@ impl Connection { let sender = self.sender.clone(); let child = spawn_thread("reader", || Connection::reader_thread(reader, sender)); if let Err(e) = self.handle_replies(receiver) { - error!( - "[{}] connection handling failed: {}", - self.addr, - e.display_chain().to_string() - ); + if is_disconnect(&e) { + // client went away mid-exchange (broken pipe / reset) — not actionable + debug!("[{}] connection closed by client: {}", self.addr, e); + } else { + error!( + "[{}] connection handling failed: {}", + self.addr, + e.display_chain().to_string() + ); + } } self.stats.clients.dec(); self.stats @@ -697,6 +702,25 @@ impl Connection { } } +/// True if the error chain is rooted in a client disconnect (broken pipe / +/// connection reset / aborted), which is expected and shouldn't be logged as ERROR. +fn is_disconnect(err: &Error) -> bool { + use std::io::ErrorKind::*; + let mut cause: Option<&(dyn std::error::Error + 'static)> = Some(err); + while let Some(e) = cause { + if let Some(io_err) = e.downcast_ref::() { + if matches!( + io_err.kind(), + BrokenPipe | ConnectionReset | ConnectionAborted | UnexpectedEof + ) { + return true; + } + } + cause = e.source(); + } + false +} + #[trace] fn get_history( query: &Query,