This part will never get exec_info and similar things when using a logging.handlers.QueueHandler.
It's because this bad boy prepares the log record for pickling no matter if it's necessary (e.g. multiprocessing.Queue) or not (e.g. queue.Queue). It just slaps the exception traceback and other things onto the log message and sets them to None 🙄 So yeah, the code won't work as you might expect.
A quick solution would be to use a custom queue handler. Something like this:
class ThreadQueueHandler(logging.handlers.QueueHandler):
"""
A queue handler that assumes a logging queue that is only used for inter-thread communication.
This avoids having to prepare the record to be pickleable.
"""
@typing.override
def prepare(self, record: logging.LogRecord) -> logging.LogRecord:
return record