Repository: litestar-org/litestar

Issue:
[enhancement] logging setup to be non-blocking
The default logging config, which is just a simple abstraction on top of DictConfig, creates a standard python logger. The issue with this setup is that python logging is sync and blocking. This means that the logging config needs to be updated to setup a QueueHandler. 

Here are some resources regarding this:

1. https://rob-blackbourn.medium.com/how-to-use-python-logging-queuehandler-with-dictconfig-1e8b1284e27a
2. https://stackoverflow.com/questions/25585518/python-logging-logutils-with-queuehandler-and-queuelistener
3. https://betterprogramming.pub/power-up-your-python-logging-6dd3ed38f322




Discussion:
We use [this](https://python-logstash-async.readthedocs.io/en/stable/) library with logstash, it's awesome. 
Hello, I would like to contribute and work on this issue. 
> Hello, I would like to contribute and work on this issue. 

Great, looking forward to your PR 😃

Resolving patch:
diff --git a/starlite/__init__.py b/starlite/__init__.py
--- a/starlite/__init__.py
+++ b/starlite/__init__.py
@@ -44,6 +44,7 @@
 from .params import Body, Parameter
 from .plugins import PluginProtocol
 from .provide import Provide
+from .queue_listener_handler import QueueListenerHandler
 from .response import Response
 from .routing import BaseRoute, HTTPRoute, Router, WebSocketRoute
 from .testing import TestClient, create_test_client, create_test_request
@@ -96,6 +97,7 @@
     "Template",
     "TemplateConfig",
     "TestClient",
+    "QueueListenerHandler",
     "ValidationException",
     "WebSocket",
     "WebSocketRoute",
diff --git a/starlite/logging.py b/starlite/logging.py
--- a/starlite/logging.py
+++ b/starlite/logging.py
@@ -14,12 +14,13 @@ class LoggingConfig(BaseModel):
         "standard": {"format": "%(levelname)s - %(asctime)s - %(name)s - %(module)s - %(message)s"}
     }
     handlers: Dict[str, Dict[str, Any]] = {
-        "console": {"class": "logging.StreamHandler", "level": "DEBUG", "formatter": "standard"}
+        "console": {"class": "logging.StreamHandler", "level": "DEBUG", "formatter": "standard"},
+        "queue_listener": {"class": "starlite.QueueListenerHandler", "handlers": ["cfg://handlers.console"]},
     }
     loggers: Dict[str, Dict[str, Any]] = {
         "starlite": {
             "level": "INFO",
-            "handlers": ["console"],
+            "handlers": ["queue_listener"],
         },
     }
     root: Dict[str, Union[Dict[str, Any], List[Any], str]] = {"handlers": ["console"], "level": "WARNING"}
diff --git a/starlite/queue_listener_handler.py b/starlite/queue_listener_handler.py
new file mode 100644
--- /dev/null
+++ b/starlite/queue_listener_handler.py
@@ -0,0 +1,46 @@
+from atexit import register
+from logging.config import ConvertingList  # type: ignore
+from logging.handlers import QueueHandler, QueueListener
+from queue import Queue
+from typing import List
+
+
+def _resolve_handlers(handlers: List[str]) -> List:
+    """
+    Converts list of string of handlers to the object of respective handler.
+    Indexing the list performs the evaluation of the object.
+    """
+    if not isinstance(handlers, ConvertingList):
+        return handlers
+
+    return [handlers[i] for i in range(len(handlers))]
+
+
+class QueueListenerHandler(QueueHandler):
+    """
+    Configures queue listener and handler to support non-blocking logging configuration.
+    """
+
+    def __init__(
+        self, handlers: List, respect_handler_level: bool = False, auto_run: bool = True, queue: Queue = Queue(-1)
+    ):
+        super().__init__(queue)
+        self.handlers = _resolve_handlers(handlers)
+        self._listener: QueueListener = QueueListener(
+            self.queue, *self.handlers, respect_handler_level=respect_handler_level
+        )
+        if auto_run:
+            self.start()
+            register(self.stop)
+
+    def start(self) -> None:
+        """
+        starts the listener.
+        """
+        self._listener.start()
+
+    def stop(self) -> None:
+        """
+        Manually stop a listener.
+        """
+        self._listener.stop()


Test patch:
diff --git a/tests/queue_handler/__init__.py b/tests/queue_handler/__init__.py
new file mode 100644
diff --git a/tests/queue_handler/test_queue_handler.py b/tests/queue_handler/test_queue_handler.py
new file mode 100644
--- /dev/null
+++ b/tests/queue_handler/test_queue_handler.py
@@ -0,0 +1,27 @@
+import logging
+from logging import StreamHandler
+
+from _pytest.logging import LogCaptureFixture
+
+from starlite.logging import LoggingConfig
+
+config = LoggingConfig(root={"handlers": ["queue_listener"], "level": "WARNING"})
+config.configure()
+logger = logging.getLogger()
+
+
+def test_logger(caplog: LogCaptureFixture) -> None:
+    """
+    Test to check logging output contains the logged message
+    """
+    caplog.set_level(logging.INFO)
+    logger.info("Testing now!")
+    assert "Testing now!" in caplog.text
+
+
+def test_resolve_handler() -> None:
+    """
+    Tests resolve handler
+    """
+    handlers = logger.handlers
+    assert isinstance(handlers[0].handlers[0], StreamHandler)  # type: ignore