fix(logging): name an unavailable log file once instead of silently dropping records
Builds on the salvaged EIO suppression: the reporter asked that logging "degrade gracefully and identify the affected path". Print one stderr line naming the file and errno when the stream first fails, reset the flag when `_open()` succeeds again so a later failure is reported anew. The contributor's test is replaced by one invariant: five records through a stream raising EIO produce zero tracebacks and exactly one path mention, and the next emit recovers into the real file.
This commit is contained in:
+10
-3
@@ -267,6 +267,7 @@ class _ManagedRotatingFileHandler(RotatingFileHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
from hermes_cli.config import is_managed
|
||||
self._managed = is_managed()
|
||||
self._unavailable_reported = False
|
||||
super().__init__(*args, **kwargs)
|
||||
self._record_stream_stat()
|
||||
|
||||
@@ -336,9 +337,14 @@ class _ManagedRotatingFileHandler(RotatingFileHandler):
|
||||
if _is_windows_concurrent_log_lock_timeout(exc):
|
||||
return
|
||||
if _is_unavailable_log_stream(exc):
|
||||
# The QueueListener must not turn a transient filesystem failure
|
||||
# into a traceback for every queued record. Drop the stale stream;
|
||||
# the next emit will reopen it if the destination has recovered.
|
||||
# The QueueListener must not turn a failing log destination into a traceback for
|
||||
# every queued record. Name the path once, drop the stale stream; the next emit
|
||||
# reopens it if the destination has recovered.
|
||||
if not self._unavailable_reported:
|
||||
self._unavailable_reported = True
|
||||
_quietly(lambda: print(
|
||||
f"hermes_logging: {self.baseFilename} unavailable ({exc}); "
|
||||
"file logging paused until it recovers", file=_safe_stderr()))
|
||||
if self.stream is not None:
|
||||
_quietly(self.stream.close)
|
||||
self.stream = None # type: ignore[assignment]
|
||||
@@ -348,6 +354,7 @@ class _ManagedRotatingFileHandler(RotatingFileHandler):
|
||||
def _open(self):
|
||||
stream = super()._open()
|
||||
self._chmod_if_managed()
|
||||
self._unavailable_reported = False # recovered: report again if it breaks anew
|
||||
return stream
|
||||
|
||||
def doRollover(self):
|
||||
|
||||
@@ -613,7 +613,7 @@ def test_eio_from_file_handler_names_the_path_once_then_recovers(tmp_path, capsy
|
||||
def writable(self):
|
||||
return True
|
||||
|
||||
def write(self, _s):
|
||||
def write(self, *_a):
|
||||
raise OSError(5, "Input/output error")
|
||||
|
||||
seek = tell = flush = write
|
||||
|
||||
Reference in New Issue
Block a user