@@ -183,6 +183,23 @@ def _set_logging_handler(
183183 )
184184
185185
186+ def _apply_logger_filters (
187+ logger_filter_mapping : dict [LoggerName , list [MessageSubstring ]],
188+ ) -> None :
189+ """Apply filters to specific loggers."""
190+ for logger_name , filtered_routes in logger_filter_mapping .items ():
191+ logger = logging .getLogger (logger_name )
192+ if not logger .hasHandlers ():
193+ _logger .warning (
194+ "Logger %s does not have any handlers. Filter will not be added." ,
195+ logger_name ,
196+ )
197+ continue
198+
199+ log_filter = GeneralLogFilter (filtered_routes )
200+ logger .addFilter (log_filter )
201+
202+
186203def config_all_loggers (
187204 * ,
188205 log_format_local_dev_enabled : bool ,
@@ -218,17 +235,50 @@ def config_all_loggers(
218235 )
219236
220237 # Apply filters
221- for logger_name , filtered_routes in logger_filter_mapping .items ():
222- logger = logging .getLogger (logger_name )
223- if not logger .hasHandlers ():
224- _logger .warning (
225- "Logger %s does not have any handlers. Filter will not be added." ,
226- logger_name ,
227- )
228- continue
238+ _apply_logger_filters (logger_filter_mapping )
229239
230- log_filter = GeneralLogFilter (filtered_routes )
231- logger .addFilter (log_filter )
240+
241+ @asynccontextmanager
242+ async def setup_async_loggers (
243+ * ,
244+ log_format_local_dev_enabled : bool = False ,
245+ logger_filter_mapping : dict [LoggerName , list [MessageSubstring ]] | None = None ,
246+ tracing_settings : TracingSettings | None = None ,
247+ ) -> AsyncGenerator [None , None ]:
248+ """
249+ Async context manager for non-blocking logging infrastructure.
250+
251+ Usage:
252+ async with setup_async_loggers(log_format_local_dev_enabled=True):
253+ # Your async application code here
254+ logger.info("This is non-blocking!")
255+
256+ Args:
257+ log_format_local_dev_enabled: Enable local development formatting
258+ logger_filter_mapping: Mapping of logger names to filtered message substrings
259+ tracing_settings: OpenTelemetry tracing configuration
260+ """
261+ # Create format string
262+ fmt = _setup_format_string (
263+ tracing_settings = tracing_settings ,
264+ log_format_local_dev_enabled = log_format_local_dev_enabled ,
265+ )
266+
267+ # Start the async logging context
268+ async with AsyncLoggingContext (
269+ log_format_local_dev_enabled = log_format_local_dev_enabled ,
270+ fmt = fmt ,
271+ ):
272+ # Apply filters if provided
273+ if logger_filter_mapping :
274+ _apply_logger_filters (logger_filter_mapping )
275+
276+ _logger .info ("Async logging setup completed" )
277+
278+ try :
279+ yield
280+ finally :
281+ _logger .debug ("Async logging context exiting" )
232282
233283
234284class LogExceptionsKwargsDict (TypedDict , total = True ):
@@ -484,17 +534,15 @@ def set_parent_module_log_level(current_module: str, desired_log_level: int) ->
484534class AsyncLoggingContext :
485535 """
486536 Async context manager for non-blocking logging infrastructure.
487- Based on the pattern from SuperFastPython article and integrated with background_task .
537+ Based on the pattern from SuperFastPython article.
488538 """
489539
490540 def __init__ (
491541 self ,
492542 * ,
493- handlers : list [logging .Handler ] | None = None ,
494543 log_format_local_dev_enabled : bool = False ,
495544 fmt : str | None = None ,
496545 ) -> None :
497- self .handlers = handlers or [logging .StreamHandler ()]
498546 self .log_format_local_dev_enabled = log_format_local_dev_enabled
499547 self .fmt = fmt or _DEFAULT_FORMATTING
500548 self .queue : queue .Queue | None = None
@@ -516,20 +564,18 @@ async def _setup_async_logging(self) -> None:
516564 # Create unlimited queue for log messages
517565 self .queue = queue .Queue ()
518566
519- # Configure handlers with proper formatting
520- formatted_handlers = []
521- for handler in self .handlers :
522- handler .setFormatter (
523- CustomFormatter (
524- self .fmt ,
525- log_format_local_dev_enabled = self .log_format_local_dev_enabled ,
526- )
567+ # Use default StreamHandler with proper formatting
568+ handler = logging .StreamHandler ()
569+ handler .setFormatter (
570+ CustomFormatter (
571+ self .fmt ,
572+ log_format_local_dev_enabled = self .log_format_local_dev_enabled ,
527573 )
528- formatted_handlers . append ( handler )
574+ )
529575
530576 # Create and start the queue listener
531577 self .listener = logging .handlers .QueueListener (
532- self .queue , * formatted_handlers , respect_handler_level = True
578+ self .queue , handler , respect_handler_level = True
533579 )
534580 self .listener .start ()
535581
@@ -542,25 +588,22 @@ async def _setup_async_logging(self) -> None:
542588 _logger .info ("Async logging context initialized with unlimited queue" )
543589
544590 async def _configure_loggers (self ) -> None :
545- """Replace all logger handlers with queue handler ."""
591+ """Add queue handler to all loggers while preserving existing handlers ."""
546592 # Get all loggers
547593 manager : logging .Manager = logging .Logger .manager
548594 root_logger = logging .getLogger ()
549595 all_loggers = [root_logger ] + [
550596 logging .getLogger (name ) for name in manager .loggerDict
551597 ]
552598
553- # Store original handlers and replace with queue handler
599+ # Store original handlers and add queue handler
554600 for logger in all_loggers :
555601 logger_name = logger .name or "root"
556602
557603 # Store original handlers
558604 self .original_handlers [logger_name ] = logger .handlers [:]
559605
560- # Clear existing handlers
561- logger .handlers .clear ()
562-
563- # Add queue handler
606+ # Add queue handler alongside existing handlers
564607 if self .queue_handler :
565608 logger .addHandler (self .queue_handler )
566609
@@ -570,22 +613,17 @@ async def _configure_loggers(self) -> None:
570613 async def _cleanup_async_logging (self ) -> None :
571614 """Restore original logging configuration."""
572615 try :
573- # Restore original handlers
616+ # Remove queue handlers from all loggers
574617 manager : logging .Manager = logging .Logger .manager
575618 root_logger = logging .getLogger ()
576619 all_loggers = [root_logger ] + [
577620 logging .getLogger (name ) for name in manager .loggerDict
578621 ]
579622
580623 for logger in all_loggers :
581- logger_name = logger .name or "root"
582- if logger_name in self .original_handlers :
583- # Clear queue handlers
584- logger .handlers .clear ()
585-
586- # Restore original handlers
587- for handler in self .original_handlers [logger_name ]:
588- logger .addHandler (handler )
624+ # Remove only the queue handler we added
625+ if self .queue_handler and self .queue_handler in logger .handlers :
626+ logger .removeHandler (self .queue_handler )
589627
590628 # Stop the queue listener
591629 if self .listener :
@@ -611,66 +649,3 @@ def get_metrics(self) -> dict[str, Any] | None:
611649 "listener_active" : self .listener is not None ,
612650 }
613651 return None
614-
615-
616- @asynccontextmanager
617- async def setup_async_loggers (
618- * ,
619- log_format_local_dev_enabled : bool = False ,
620- logger_filter_mapping : dict [LoggerName , list [MessageSubstring ]] | None = None ,
621- tracing_settings : TracingSettings | None = None ,
622- handlers : list [logging .Handler ] | None = None ,
623- ) -> AsyncGenerator [None , None ]:
624- """
625- Async context manager for non-blocking logging infrastructure.
626-
627- Usage:
628- async with setup_async_loggers(log_format_local_dev_enabled=True):
629- # Your async application code here
630- logger.info("This is non-blocking!")
631-
632- Args:
633- log_format_local_dev_enabled: Enable local development formatting
634- logger_filter_mapping: Mapping of logger names to filtered message substrings
635- tracing_settings: OpenTelemetry tracing configuration
636- handlers: Custom handlers to use (defaults to StreamHandler)
637- """
638- # Create format string
639- fmt = _setup_format_string (
640- tracing_settings = tracing_settings ,
641- log_format_local_dev_enabled = log_format_local_dev_enabled ,
642- )
643-
644- # Start the async logging context
645- async with AsyncLoggingContext (
646- handlers = handlers ,
647- log_format_local_dev_enabled = log_format_local_dev_enabled ,
648- fmt = fmt ,
649- ):
650- # Apply filters if provided
651- if logger_filter_mapping :
652- _apply_logger_filters (logger_filter_mapping )
653-
654- _logger .info ("Async logging setup completed" )
655-
656- try :
657- yield
658- finally :
659- _logger .debug ("Async logging context exiting" )
660-
661-
662- def _apply_logger_filters (
663- logger_filter_mapping : dict [LoggerName , list [MessageSubstring ]],
664- ) -> None :
665- """Apply filters to specific loggers."""
666- for logger_name , filtered_routes in logger_filter_mapping .items ():
667- logger = logging .getLogger (logger_name )
668- if not logger .hasHandlers ():
669- _logger .warning (
670- "Logger %s does not have any handlers. Filter will not be added." ,
671- logger_name ,
672- )
673- continue
674-
675- log_filter = GeneralLogFilter (filtered_routes )
676- logger .addFilter (log_filter )
0 commit comments