log(webchat): raise SSE timeout and error lifecycle logs

Raise WebChat SSE timeout and non-benign error lifecycle logs to INFO while keeping routine client disconnects at DEBUG, preserving detach-based subscriber lifecycle semantics.
This commit is contained in:
倪程伟 2026-08-11 15:45:24 +08:00 committed by GitHub
parent 55cf53b9b4
commit a11c7ccb52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -196,14 +196,23 @@ public class WebChatController {
streamTracker.detach(conversationId, emitter);
});
emitter.onTimeout(() -> {
log.debug("[WebChat] SSE timeout: {}", conversationId);
// INFO: a timeout means the stream went idle past the SseEmitter
// budget, which is a key signal when diagnosing stream stalls.
log.info("[WebChat] SSE timeout (stream went idle past the emitter budget): {}", conversationId);
streamTracker.detach(conversationId, emitter);
// Explicitly complete after timeout so the servlet container does
// not rethrow AsyncRequestTimeoutException.
emitter.complete();
});
emitter.onError(e -> {
log.debug("[WebChat] SSE error: {} - {}", conversationId, e.getMessage());
// INFO only for non-benign causes; a client simply closing the tab
// (broken pipe / connection reset) is routine and stays DEBUG so it
// doesn't flood production logs.
if (isClientDisconnect(e)) {
log.debug("[WebChat] SSE client disconnected: {} - {}", conversationId, e.getMessage());
} else {
log.info("[WebChat] SSE error: {} - {}", conversationId, e.getMessage());
}
streamTracker.detach(conversationId, emitter);
});
@ -1262,12 +1271,16 @@ public class WebChatController {
streamTracker.detach(conversationId, emitter);
});
emitter.onTimeout(() -> {
log.debug("[WebChat] approve SSE timeout: {}", conversationId);
log.info("[WebChat] approve SSE timeout (stream went idle past the emitter budget): {}", conversationId);
streamTracker.detach(conversationId, emitter);
emitter.complete();
});
emitter.onError(e -> {
log.debug("[WebChat] approve SSE error: {} - {}", conversationId, e.getMessage());
if (isClientDisconnect(e)) {
log.debug("[WebChat] approve SSE client disconnected: {} - {}", conversationId, e.getMessage());
} else {
log.info("[WebChat] approve SSE error: {} - {}", conversationId, e.getMessage());
}
streamTracker.detach(conversationId, emitter);
});
@ -1417,6 +1430,22 @@ public class WebChatController {
}
}
/**
* True when the SSE error is a routine client-side disconnect (closed tab,
* network drop) rather than a server-side failure. Used to keep the
* lifecycle log noise down: a visitor closing the tab is expected and
* stays DEBUG; anything else is worth an INFO line for production triage.
* Mirrors ChatController#isClientDisconnect.
*/
private static boolean isClientDisconnect(Throwable e) {
if (e instanceof IOException) return true;
String msg = e.getMessage();
if (msg == null) return false;
String lower = msg.toLowerCase();
return lower.contains("broken pipe") || lower.contains("connection reset")
|| lower.contains("client abort") || lower.contains("closed");
}
/**
* Broadcast a {@code tool_approval_resolved} event so the SDK clears its
* approval banner in real time. Shared by approve / deny / stop-sweep.