11import asyncio
2- import atexit
3- import logging
42import logging .handlers
5- import time
3+ import threading
64import traceback
5+ import warnings
76from contextlib import suppress
8- from functools import partial
7+ from queue import Empty , Queue
98from socket import socket
109from typing import (
1110 Any , Callable , Dict , Iterable , List , Optional , Tuple , Type , Union ,
1211)
13- from weakref import finalize
1412
1513import aiomisc_log
1614from aiomisc_log .enum import LogFormat , LogLevel
1715
18- from .thread_pool import run_in_new_thread
19-
20-
21- def _thread_flusher (
22- handler : logging .handlers .MemoryHandler ,
23- flush_interval : Union [float , int ],
24- loop : asyncio .AbstractEventLoop ,
25- ) -> None :
26- def has_no_target () -> bool :
27- return True
28-
29- def has_target () -> bool :
30- return bool (handler .target )
31-
32- is_target = has_no_target
33-
34- if isinstance (handler , logging .handlers .MemoryHandler ):
35- is_target = has_target
36-
37- while not loop .is_closed () and is_target ():
38- with suppress (Exception ):
39- if handler .buffer :
40- handler .flush ()
41-
42- time .sleep (flush_interval )
16+ from .counters import Statistic
17+
18+
19+ class ThreadedHandlerStatistic (Statistic ):
20+ threads : int
21+ records : int
22+ errors : int
23+ flushes : int
24+
25+
26+ class ThreadedHandler (logging .Handler ):
27+ def __init__ (
28+ self , target : logging .Handler , flush_interval : float = 0.1 ,
29+ buffered : bool = True , queue_size : int = 0 ,
30+ ):
31+ super ().__init__ ()
32+ self ._buffered = buffered
33+ self ._target = target
34+ self ._flush_interval = flush_interval
35+ self ._flush_event = threading .Event ()
36+ self ._queue : Queue [Optional [logging .LogRecord ]] = Queue (queue_size )
37+ self ._close_event = threading .Event ()
38+ self ._thread = threading .Thread (target = self ._in_thread , daemon = True )
39+ self ._statistic = ThreadedHandlerStatistic ()
40+
41+ def start (self ) -> None :
42+ self ._statistic .threads += 1
43+ self ._thread .start ()
44+
45+ def close (self ) -> None :
46+ self ._queue .put (None )
47+ del self ._queue
48+ self .flush ()
49+ self ._close_event .set ()
50+ super ().close ()
51+
52+ def flush (self ) -> None :
53+ self ._statistic .flushes += 1
54+ self ._flush_event .set ()
55+
56+ def emit (self , record : logging .LogRecord ) -> None :
57+ if self ._buffered :
58+ self ._queue .put_nowait (record )
59+ else :
60+ self ._queue .put (record )
61+ self ._statistic .records += 1
62+
63+ def _in_thread (self ) -> None :
64+ queue = self ._queue
65+ while not self ._close_event .is_set ():
66+ self ._flush_event .wait (self ._flush_interval )
67+ try :
68+ self .acquire ()
69+ while True :
70+ record = queue .get (timeout = self ._flush_interval )
71+ if record is None :
72+ return
73+ with suppress (Exception ):
74+ self ._target .handle (record )
75+ except Empty :
76+ pass
77+ finally :
78+ self .release ()
79+ self ._statistic .threads -= 1
4380
4481
4582def suppressor (
@@ -54,27 +91,18 @@ def wrapper() -> None:
5491
5592def wrap_logging_handler (
5693 handler : logging .Handler ,
57- loop : Optional [asyncio .AbstractEventLoop ] = None ,
5894 buffer_size : int = 1024 ,
5995 flush_interval : Union [float , int ] = 0.1 ,
96+ loop : Optional [asyncio .AbstractEventLoop ] = None ,
6097) -> logging .Handler :
61- buffered_handler = logging . handlers . MemoryHandler (
62- buffer_size ,
98+ warnings . warn ( "wrap_logging_handler is deprecated" , DeprecationWarning )
99+ handler = ThreadedHandler (
63100 target = handler ,
64- flushLevel = logging .CRITICAL ,
65- )
66-
67- run_in_new_thread (
68- _thread_flusher , args = (
69- buffered_handler , flush_interval , loop ,
70- ), no_return = True , statistic_name = "logger" ,
101+ queue_size = buffer_size ,
102+ flush_interval = flush_interval ,
71103 )
72-
73- at_exit_flusher = suppressor (handler .flush )
74- atexit .register (at_exit_flusher )
75- finalize (buffered_handler , partial (atexit .unregister , at_exit_flusher ))
76-
77- return buffered_handler
104+ handler .start ()
105+ return handler
78106
79107
80108class UnhandledLoopHook (aiomisc_log .UnhandledHook ):
@@ -109,7 +137,7 @@ def __call__(
109137 protocol : Optional [asyncio .Protocol ] = context .pop ("protocol" , None )
110138 transport : Optional [asyncio .Transport ] = context .pop ("transport" , None )
111139 sock : Optional [socket ] = context .pop ("socket" , None )
112- source_traceback : List [traceback .FrameSummary ] = (
140+ source_tb : List [traceback .FrameSummary ] = (
113141 context .pop ("source_traceback" , None ) or []
114142 )
115143
@@ -129,51 +157,52 @@ def __call__(
129157
130158 self ._fill_transport_extra (transport , extra )
131159 self .logger .exception (message , exc_info = exception , extra = extra )
132- if source_traceback :
133- self .logger .error (
134- "" .join (traceback .format_list (source_traceback )),
135- )
160+ if source_tb :
161+ self .logger .error ("" .join (traceback .format_list (source_tb )))
136162
137163
138164def basic_config (
139165 level : Union [int , str ] = LogLevel .default (),
140166 log_format : Union [str , LogFormat ] = LogFormat .default (),
141- buffered : bool = True , buffer_size : int = 1024 ,
167+ buffered : bool = True ,
168+ buffer_size : int = 0 ,
142169 flush_interval : Union [int , float ] = 0.2 ,
143170 loop : Optional [asyncio .AbstractEventLoop ] = None ,
144171 handlers : Iterable [logging .Handler ] = (),
145172 ** kwargs : Any ,
146173) -> None :
147- loop = loop or asyncio .get_event_loop ()
148174 unhandled_hook = UnhandledLoopHook (logger_name = "asyncio.unhandled" )
149175
150- def wrap_handler ( handler : logging . Handler ) -> logging . Handler :
151- nonlocal buffer_size , buffered , loop , unhandled_hook
176+ if loop is None :
177+ loop = asyncio . get_event_loop ()
152178
153- unhandled_hook .add_handler (handler )
179+ forever_task = asyncio .gather (
180+ loop .create_future (), return_exceptions = True ,
181+ )
182+ loop .set_exception_handler (unhandled_hook )
154183
155- if buffered :
156- return wrap_logging_handler (
157- handler = handler ,
158- buffer_size = buffer_size ,
159- flush_interval = flush_interval ,
160- loop = loop ,
161- )
162- return handler
184+ log_handlers = []
185+
186+ for user_handler in handlers :
187+ handler = ThreadedHandler (
188+ buffered = buffered ,
189+ flush_interval = flush_interval ,
190+ queue_size = buffer_size ,
191+ target = user_handler ,
192+ )
193+ unhandled_hook .add_handler (handler )
194+ forever_task .add_done_callback (lambda _ : handler .close ())
195+ log_handlers .append (handler )
196+ handler .start ()
163197
164198 aiomisc_log .basic_config (
165- level = level ,
166- log_format = log_format ,
167- handler_wrapper = wrap_handler ,
168- handlers = handlers ,
169- ** kwargs ,
199+ level = level , log_format = log_format , handlers = log_handlers , ** kwargs ,
170200 )
171201
172- loop .set_exception_handler (unhandled_hook )
173-
174202
175203__all__ = (
176204 "LogFormat" ,
177205 "LogLevel" ,
178206 "basic_config" ,
207+ "ThreadedHandler" ,
179208)
0 commit comments