1 """Logging utilities."""
3 from __future__
import annotations
5 from collections.abc
import Callable, Coroutine
6 from functools
import partial, wraps
12 from typing
import Any, cast, overload
18 get_hassjob_callable_job_type,
23 """Process the log in another thread."""
25 listener: logging.handlers.QueueListener |
None =
None
27 def handle(self, record: logging.LogRecord) -> Any:
28 """Conditionally emit the specified logging record.
30 Depending on which filters have been added to the handler, push the new
31 records onto the backing Queue.
33 The default python logger Handler acquires a lock
34 in the parent class which we do not need as
35 SimpleQueue is already thread safe.
37 See https://bugs.python.org/issue24645
39 return_value = self.filter(record)
45 """Tidy up any resources used by the handler.
47 This adds shutdown of the QueueListener
58 """Migrate the existing log handlers to use the queue.
60 This allows us to avoid blocking I/O and formatting messages
61 in the event loop as log messages are written in another thread.
63 simple_queue: queue.SimpleQueue[logging.Handler] = queue.SimpleQueue()
65 logging.root.addHandler(queue_handler)
67 migrated_handlers: list[logging.Handler] = []
68 for handler
in logging.root.handlers[:]:
69 if handler
is queue_handler:
71 logging.root.removeHandler(handler)
72 migrated_handlers.append(handler)
74 listener = logging.handlers.QueueListener(simple_queue, *migrated_handlers)
75 queue_handler.listener = listener
80 def log_exception[*_Ts](format_err: Callable[[*_Ts], Any], *args: *_Ts) ->
None:
81 """Log an exception with additional context."""
82 module = inspect.getmodule(inspect.stack(context=0)[1].frame)
83 if module
is not None:
84 module_name = module.__name__
89 module_name = __name__
92 frames = len(inspect.trace()) - 1
93 exc_msg = traceback.format_exc(-frames)
94 friendly_msg = format_err(*args)
95 logging.getLogger(module_name).error(
"%s\n%s", friendly_msg, exc_msg)
98 async
def _async_wrapper[*_Ts](
99 async_func: Callable[[*_Ts], Coroutine[Any, Any,
None]],
100 format_err: Callable[[*_Ts], Any],
103 """Catch and log exception."""
105 await async_func(*args)
107 log_exception(format_err, *args)
110 def _sync_wrapper[*_Ts](
111 func: Callable[[*_Ts], Any], format_err: Callable[[*_Ts], Any], *args: *_Ts
113 """Catch and log exception."""
117 log_exception(format_err, *args)
121 def _callback_wrapper[*_Ts](
122 func: Callable[[*_Ts], Any], format_err: Callable[[*_Ts], Any], *args: *_Ts
124 """Catch and log exception."""
128 log_exception(format_err, *args)
132 def catch_log_exception[*_Ts](
133 func: Callable[[*_Ts], Coroutine[Any, Any, Any]],
134 format_err: Callable[[*_Ts], Any],
135 job_type: HassJobType |
None =
None,
136 ) -> Callable[[*_Ts], Coroutine[Any, Any,
None]]: ...
140 def catch_log_exception[*_Ts](
141 func: Callable[[*_Ts], Any],
142 format_err: Callable[[*_Ts], Any],
143 job_type: HassJobType |
None =
None,
144 ) -> Callable[[*_Ts],
None] | Callable[[*_Ts], Coroutine[Any, Any,
None]]: ...
147 def catch_log_exception[*_Ts](
148 func: Callable[[*_Ts], Any],
149 format_err: Callable[[*_Ts], Any],
150 job_type: HassJobType |
None =
None,
151 ) -> Callable[[*_Ts],
None] | Callable[[*_Ts], Coroutine[Any, Any,
None]]:
152 """Decorate a function func to catch and log exceptions.
154 If func is a coroutine function, a coroutine function will be returned.
155 If func is a callback, a callback will be returned.
160 if job_type
is HassJobType.Coroutinefunction:
161 async_func = cast(Callable[[*_Ts], Coroutine[Any, Any,
None]], func)
162 return wraps(async_func)(partial(_async_wrapper, async_func, format_err))
164 if job_type
is HassJobType.Callback:
165 return wraps(func)(partial(_callback_wrapper, func, format_err))
167 return wraps(func)(partial(_sync_wrapper, func, format_err))
170 def catch_log_coro_exception[_T, *_Ts](
171 target: Coroutine[Any, Any, _T], format_err: Callable[[*_Ts], Any], *args: *_Ts
172 ) -> Coroutine[Any, Any, _T |
None]:
173 """Decorate a coroutine to catch and log exceptions."""
175 async
def coro_wrapper(*args: *_Ts) -> _T |
None:
176 """Catch and log exception."""
180 log_exception(format_err, *args)
183 return coro_wrapper(*args)
186 def async_create_catching_coro[_T](
187 target: Coroutine[Any, Any, _T],
188 ) -> Coroutine[Any, Any, _T |
None]:
189 """Wrap a coroutine to catch and log exceptions.
191 The exception will be logged together with a stacktrace of where the
192 coroutine was wrapped.
194 target: target coroutine.
196 trace = traceback.extract_stack()
197 return catch_log_coro_exception(
200 f
"Exception in {target.__name__} called from\n"
201 +
"".join(traceback.format_list(trace[:-1]))
Any handle(self, logging.LogRecord record)
HassJobType get_hassjob_callable_job_type(Callable[..., Any] target)
None async_activate_log_queue_handler(HomeAssistant hass)