Repository: Delgan/loguru (Python)

Issue:
Internal errors when "enqueue=True" and "catch=False" should not invalidate the handler
Let's say some message fail to be logged (due to `Exception` raised during pickling or by the sink itself). If `catch=False`, the `Exception` will be re-raised, but it the user won't be able to catch it because it's raised from a internal thread. The error will be reported on `sys.stderr` by Python handler, but the thread will die, thus any all the subsequent logging messages will be discarded.

```python
import random
from loguru import logger

def broken_sink(message):
    if random.random() < 0.5:
        raise RuntimeError("Error")
    print(message, end="")

logger.remove()
logger.add(broken_sink, enqueue=True, catch=False, colorize=True)

for i in range(10):
    logger.info(f"Message #{i}")
```

I think this behavior is not user-friendly and does have much interest. The `catch=False` makes sense to let user manage exception handling, which is not possible when `enqueue=True` anyway.

When `enqueue=False` and `catch=False`, the caller thread will die, but the sink will remain usable by others threads. The behavior should be the same when `enqueue=True`.

Also, it could simplify the code.


Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index e884efb4c..a557d59de 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -4,6 +4,7 @@
 - Update ``InterceptHandler`` recipe to make it compatible with Python 3.11 (`#654 <https://github.com/Delgan/loguru/issues/654>`_).
 - Add a new ``watch`` optional argument to file sinks in order to automatically re-create possibly deleted or changed file (`#471 <https://github.com/Delgan/loguru/issues/471>`_).
 - Make ``patch()`` calls cumulative instead of overriding the possibly existing patching function (`#462 <https://github.com/Delgan/loguru/issues/462>`_).
+- Make sinks added with ``enqueue=True`` and ``catch=False`` still process logged messages in case of internal exception (`#833 <https://github.com/Delgan/loguru/issues/833>`_).
 - Avoid possible deadlocks caused by re-using the logger inside a sink, a signal handler or a ``__del__`` method. Since the logger is not re-entrant, such misuse will be detected and will now generate a ``RuntimeError`` (`#712 <https://github.com/Delgan/loguru/issues/712>`_, thanks `@jacksmith15 <https://github.com/jacksmith15>`_).
 - Fix file sink rotation using an aware ``datetime.time`` for which the timezone was ignored (`#697 <https://github.com/Delgan/loguru/issues/697>`_).
 - Fix logs colorization not automatically enabled for Jupyter Notebook and Google Colab (`#494 <https://github.com/Delgan/loguru/issues/494>`_).
diff --git a/loguru/_handler.py b/loguru/_handler.py
index 456331403..d4563e507 100644
--- a/loguru/_handler.py
+++ b/loguru/_handler.py
@@ -291,9 +291,6 @@ def _queued_writer(self):
                 message = queue.get()
             except Exception:
                 with lock:
-                    if not self._error_interceptor.should_catch():
-                        self._confirmation_event.set()
-                        raise
                     self._error_interceptor.print(None)
                 continue
 
@@ -308,9 +305,6 @@ def _queued_writer(self):
                 try:
                     self._sink.write(message)
                 except Exception:
-                    if not self._error_interceptor.should_catch():
-                        self._confirmation_event.set()
-                        raise
                     self._error_interceptor.print(message.record)
 
     def __getstate__(self):
diff --git a/loguru/_logger.py b/loguru/_logger.py
index 52a3cc4d9..bdc1fb402 100644
--- a/loguru/_logger.py
+++ b/loguru/_logger.py
@@ -267,7 +267,7 @@ def add(
             Whether the exception trace should display the variables values to eases the debugging.
             This should be set to ``False`` in production to avoid leaking sensitive data.
         enqueue : |bool|, optional
-            Whether the messages to be logged should first pass through a multiprocess-safe queue
+            Whether the messages to be logged should first pass through a multiprocessing-safe queue
             before reaching the sink. This is useful while logging to a file through multiple
             processes. This also has the advantage of making logging calls non-blocking.
         catch : |bool|, optional


Test patch:
diff --git a/tests/test_add_option_catch.py b/tests/test_add_option_catch.py
index 4c76cf039..486f920c6 100644
--- a/tests/test_add_option_catch.py
+++ b/tests/test_add_option_catch.py
@@ -140,4 +140,4 @@ def broken_sink(m):
         logger.info("B")
         time.sleep(0.1)
 
-    assert called == 1
+    assert called == 2
diff --git a/tests/test_add_option_enqueue.py b/tests/test_add_option_enqueue.py
index 57a1d4d99..7eb772626 100644
--- a/tests/test_add_option_enqueue.py
+++ b/tests/test_add_option_enqueue.py
@@ -146,15 +146,17 @@ def test_not_caught_exception_queue_get(writer, capsys):
     with default_threading_excepthook():
         logger.info("It's fine")
         logger.bind(broken=NotUnpicklable()).info("Bye bye...")
-        logger.info("It's not fine")
+        logger.info("It's fine again")
         logger.remove()
 
     out, err = capsys.readouterr()
     lines = err.strip().splitlines()
-    assert writer.read() == "It's fine\n"
+    assert writer.read() == "It's fine\nIt's fine again\n"
     assert out == ""
-    assert lines[0].startswith("Exception")
-    assert lines[-1].endswith("UnpicklingError: You shall not de-serialize me!")
+    assert lines[0] == "--- Logging error in Loguru Handler #0 ---"
+    assert lines[1] == "Record was: None"
+    assert lines[-2].endswith("UnpicklingError: You shall not de-serialize me!")
+    assert lines[-1] == "--- End of logging error ---"
 
 
 def test_not_caught_exception_sink_write(capsys):
@@ -163,14 +165,16 @@ def test_not_caught_exception_sink_write(capsys):
     with default_threading_excepthook():
         logger.info("It's fine")
         logger.bind(fail=True).info("Bye bye...")
-        logger.info("It's not fine")
+        logger.info("It's fine again")
         logger.remove()
 
     out, err = capsys.readouterr()
     lines = err.strip().splitlines()
-    assert out == "It's fine\n"
-    assert lines[0].startswith("Exception")
-    assert lines[-1] == "RuntimeError: You asked me to fail..."
+    assert out == "It's fine\nIt's fine again\n"
+    assert lines[0] == "--- Logging error in Loguru Handler #0 ---"
+    assert re.match(r"Record was: \{.*Bye bye.*\}", lines[1])
+    assert lines[-2] == "RuntimeError: You asked me to fail..."
+    assert lines[-1] == "--- End of logging error ---"
 
 
 def test_not_caught_exception_sink_write_then_complete(capsys):
@@ -179,13 +183,16 @@ def test_not_caught_exception_sink_write_then_complete(capsys):
     with default_threading_excepthook():
         logger.bind(fail=True).info("Bye bye...")
         logger.complete()
+        logger.complete()  # Called twice to ensure it's re-usable.
         logger.remove()
 
     out, err = capsys.readouterr()
     lines = err.strip().splitlines()
     assert out == ""
-    assert lines[0].startswith("Exception")
-    assert lines[-1] == "RuntimeError: You asked me to fail..."
+    assert lines[0] == "--- Logging error in Loguru Handler #0 ---"
+    assert re.match(r"Record was: \{.*Bye bye.*\}", lines[1])
+    assert lines[-2] == "RuntimeError: You asked me to fail..."
+    assert lines[-1] == "--- End of logging error ---"
 
 
 def test_not_caught_exception_queue_get_then_complete(writer, capsys):
@@ -194,14 +201,17 @@ def test_not_caught_exception_queue_get_then_complete(writer, capsys):
     with default_threading_excepthook():
         logger.bind(broken=NotUnpicklable()).info("Bye bye...")
         logger.complete()
+        logger.complete()
         logger.remove()
 
     out, err = capsys.readouterr()
     lines = err.strip().splitlines()
     assert writer.read() == ""
     assert out == ""
-    assert lines[0].startswith("Exception")
-    assert lines[-1].endswith("UnpicklingError: You shall not de-serialize me!")
+    assert lines[0] == "--- Logging error in Loguru Handler #0 ---"
+    assert lines[1] == "Record was: None"
+    assert lines[-2].endswith("UnpicklingError: You shall not de-serialize me!")
+    assert lines[-1] == "--- End of logging error ---"
 
 
 def test_wait_for_all_messages_enqueued(capsys):