@@ -20,23 +20,48 @@ def __init__(
2020 self .min_level = min_level
2121 self .buffer = ""
2222 self .sys_file = sys_file
23+ self ._in_logging = False # Recursion guard
2324
2425 def write (self , message : str ) -> None :
2526 """Write message to the logger, buffering until newline."""
26- self .buffer += message
27- while "\n " in self .buffer :
28- line , self .buffer = self .buffer .split ("\n " , 1 )
29- # Only log if the message is not empty and the level is sufficient
30- if line and self .level >= self .min_level :
31- # The context variable is automatically available here
32- self .logger ._log (self .level , line , ())
27+ # Prevent infinite recursion when logging.handleError writes to stderr
28+ if self ._in_logging :
29+ if self .sys_file :
30+ try :
31+ self .sys_file .write (message )
32+ except (OSError , IOError ):
33+ pass # Fail silently if we can't write
34+ return
35+
36+ try :
37+ self ._in_logging = True
38+ self .buffer += message
39+ while "\n " in self .buffer :
40+ line , self .buffer = self .buffer .split ("\n " , 1 )
41+ # Only log if the message is not empty and the level is sufficient
42+ if line and self .level >= self .min_level :
43+ self .logger ._log (self .level , line , ())
44+ finally :
45+ self ._in_logging = False
3346
3447 def flush (self ) -> None :
3548 """Flush any remaining buffered messages to the logger."""
36- # Log any remaining content in the buffer on flush
37- if self .buffer and self .level >= self .min_level :
38- self .logger ._log (self .level , self .buffer , ())
39- self .buffer = ""
49+ if self ._in_logging :
50+ if self .sys_file :
51+ try :
52+ self .sys_file .flush ()
53+ except (OSError , IOError ):
54+ pass # Fail silently if we can't flush
55+ return
56+
57+ try :
58+ self ._in_logging = True
59+ # Log any remaining content in the buffer on flush
60+ if self .buffer and self .level >= self .min_level :
61+ self .logger ._log (self .level , self .buffer , ())
62+ self .buffer = ""
63+ finally :
64+ self ._in_logging = False
4065
4166 def fileno (self ) -> int :
4267 """Get the file descriptor of the original sys.stdout/sys.stderr."""
@@ -47,7 +72,10 @@ def fileno(self) -> int:
4772
4873 def isatty (self ) -> bool :
4974 """Check if the original sys.stdout/sys.stderr is a TTY."""
50- return hasattr (self .sys_file , "isatty" ) and self .sys_file .isatty ()
75+ try :
76+ return hasattr (self .sys_file , "isatty" ) and self .sys_file .isatty ()
77+ except (AttributeError , OSError , ValueError ):
78+ return False
5179
5280 def writable (self ) -> bool :
5381 """Check if the original sys.stdout/sys.stderr is writable."""
0 commit comments