@@ -42,8 +42,9 @@ class LogHelper:
4242
4343 Examples::
4444
45+ # 1. Bind a StreamHandler to the "mylogger" logger, output to sys.stdout
4546 import logging
46- from morebuiltins.log import LogHelper
47+ from morebuiltins.logs import LogHelper
4748
4849 LogHelper.shorten_level()
4950 logger = LogHelper.bind_handler(name="mylogger", filename=sys.stdout, maxBytes=100 * 1024**2, backupCount=7)
@@ -52,6 +53,23 @@ class LogHelper:
5253 assert logger is logger2
5354 logger.info("This is an info message")
5455 logger.fatal("This is a critical message")
56+
57+ # 2. Bind file and stderr in the same logger
58+ import sys
59+ import logging
60+ from morebuiltins.logs import LogHelper
61+ LogHelper.shorten_level()
62+ logger = LogHelper.bind_handler(name="mylogger", filename="mylog.log", maxBytes=100 * 1024**2, backupCount=7)
63+ logger = LogHelper.bind_handler(name="mylogger", filename=sys.stderr)
64+ logger.info("This is an info message")
65+
66+ # 3. Use queue=True to make logging non-blocking, both file and stderr
67+ import sys
68+ from morebuiltins.logs import LogHelper
69+ LogHelper.shorten_level()
70+ logger = LogHelper.bind_handler(name="mylogger", filename="mylog.log", maxBytes=100 * 1024**2, backupCount=7, queue=True)
71+ logger = LogHelper.bind_handler(name="mylogger", filename=sys.stderr, queue=True)
72+ logger.info("This is an info message")
5573 """
5674
5775 DEFAULT_FORMAT = (
@@ -192,15 +210,28 @@ def bind_handler(
192210 formatter = logging .Formatter (cls .DEFAULT_FORMAT )
193211 handler .setFormatter (formatter )
194212 # Add the handler to the logger
195- logger .addHandler (handler )
196213 if queue :
197214 if queue is True :
198215 queue = Queue ()
199216 elif not isinstance (queue , (Queue , ProcessQueue )):
200217 raise TypeError ("queue must be a Queue, ProcessQueue, True, or False" )
201- async_listener = AsyncQueueListener (logger , queue = queue )
202- async_listener .start ()
203- atexit .register (async_listener .stop )
218+ if (
219+ len (logger .handlers ) == 1
220+ and isinstance (logger .handlers [0 ], QueueHandler )
221+ and isinstance (logger .handlers [0 ].listener , AsyncQueueListener )
222+ ):
223+ # already a QueueHandler
224+ async_listener : AsyncQueueListener = logger .handlers [0 ].listener
225+ async_listener .bind_new_handler (handler )
226+ return logger
227+ else :
228+ logger .addHandler (handler )
229+ async_listener = AsyncQueueListener (logger , queue = queue )
230+ async_listener .start ()
231+ atexit .register (async_listener .stop )
232+ else :
233+ if handler not in logger .handlers :
234+ logger .addHandler (handler )
204235 return logger
205236
206237 @classmethod
@@ -671,10 +702,11 @@ def __init__(
671702 respect_handler_level = True ,
672703 ):
673704 self .logger = logger
705+ self .queue_id = id (queue )
674706 self .queue = queue or Queue ()
675- # Store original handlers
707+ # Store original handlers to restore later
676708 self .original_handlers = list (logger .handlers )
677- # Get handlers that might block
709+ # Get handlers that might block, send to parent class
678710 self .blocking_handlers = [
679711 h for h in self .original_handlers if not isinstance (h , QueueHandler )
680712 ]
@@ -684,14 +716,24 @@ def __init__(
684716 * self .blocking_handlers ,
685717 respect_handler_level = respect_handler_level ,
686718 )
719+ self ._started = self ._stopped = False
720+
721+ def bind_new_handler (self , handler : logging .Handler ):
722+ if isinstance (handler , QueueHandler ):
723+ raise TypeError ("handler cannot be a QueueHandler" )
724+ self .original_handlers .append (handler )
725+ self .blocking_handlers .append (handler )
726+ self .handlers = tuple (self .blocking_handlers )
687727
688728 def _switch_to_queue_handler (self ):
689729 """Switch handlers in a blocking context"""
690730 # Remove original handlers
691731 for handler in self .original_handlers :
692732 self .logger .removeHandler (handler )
693733 # Add queue handler
694- self .logger .addHandler (QueueHandler (self .queue ))
734+ queue_handler = QueueHandler (self .queue )
735+ queue_handler .listener = self
736+ self .logger .addHandler (queue_handler )
695737
696738 def _restore_original_handlers (self ):
697739 """Restore original handlers in a blocking context"""
@@ -702,12 +744,16 @@ def _restore_original_handlers(self):
702744 self .logger .addHandler (handler )
703745
704746 def stop (self ):
705- super ().stop ()
706- self ._restore_original_handlers ()
747+ if self ._started and not self ._stopped :
748+ self ._stopped = True
749+ super ().stop ()
750+ self ._restore_original_handlers ()
707751
708752 def start (self ):
709- self ._switch_to_queue_handler ()
710- super ().start ()
753+ if not self ._started :
754+ self ._started = True
755+ self ._switch_to_queue_handler ()
756+ super ().start ()
711757
712758 async def __aenter__ (self ):
713759 await to_thread (self .start )
0 commit comments